No Raw Pointers (Work in Progress)

The Problem
Consider native pointers of the kind “T* p.” The main difficulty with such kind of pointers is that memory is

  • sometimes deleted more than once leading to memory corruption or
  • sometimes not deleted at all leading to memory leaks.

Furthermore when a function is passed a pointer it is not obvious whether the pointer passed in must be deleted within the function or elsewhere.

Raw Pointers
“A raw pointer is pointer to an object with implied ownership and reference semantics” [1] such as

  • T* p
  • unique_ptr
  • shared_ptr

As Parent suggests raw pointers sharing memory between distinct pieces of code is no better than a global variable. Here is why. It is difficult to reason about shared pointers. Normally we expect a function to be time invariant. ‘sin(x)’ should return the same value for a given value of ‘x’ no matter when it is called and so it does. But if a piece of memory is shared then two runs of the same function with the same input can return different results because the underlying memory could be modified by another module.

Current Uses
So why are pointers still used? Optimization is one reason. Passing large containers by value is sub-optimal. The other very common reason is polymorphism. Lets take a simple example where a document is a collection of shapes. Drawing the document consists of drawing the shapes. Let us assume a shape can be just a triangle, rectangle or circle. In other words triangle, rectangle and circle would inherit from shape. The drawing code would like this

class shape {
	virtual ~shape() { }
	virtual void draw(ostream&, size_t) const = 0;
using document_t = vector<shared_ptr<shape>>;
void draw(const document_t& x, ostream& out, size_t position)
	out << string(position, ' ') << "<document>" << endl;
	for (const auto& e : x) e->draw(out, position + 2);
	out << string(position, ' ') << "</document>" << endl;

Here a document_t would be instantiated with raw pointers to concrete objects like circle, rectangle or triangle. The issue here is the curse of inheritance. Just to indicate that a type implements a function with a specific signature, each concrete object is required to inherit from a specific class and override virtual functions. A virtual pointer introduces a level of indirection that involves a performance hit.
Parent suggests the following alternative [1].

class object_t {
	template <typename T>
	object_t(T x) :
           { }
	friend void draw(
          const object_t& x, 
          ostream& out, 
          size_t position)
		x.self_->draw_(out, position);
	struct concept_t {
		virtual ~concept_t() = default;
		virtual void draw_(ostream&, size_t) const = 0;
	template <typename T>
	struct model : concept_t {
		model(T x) : data_(move(x)) { }
		void draw_(ostream& out, size_t position) const
			draw(data_, out, position);
		T data_;
	shared_ptr<const concept_t> self_;
using document_t = vector<shared_ptr<object_t>>;

Thus even though we have inheritance and virtual functions the whole code is localised to a specific class. We do not have an abstract shape class, just a generic model. Notice the object is copied and there is no external reference to it thus avoiding the curse of sharing and that ‘draw(circle&, ostream&, int)‘ is not a member function. Neither for that matter is ‘draw(object_t cons&...).’

[1] Sean Parent, “C++ Seasoning,” Going Native 2013,
[2] C.A.R. Hoare “Communicating Sequential Processes” (first published by Prentice Hall in 1985)

About The Sunday Programmer

Joe is an experienced C++/C# developer on Windows. Currently looking out for an opening in C/C++ on Windows or Linux.
This entry was posted in C++, Software Engineering and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s