1.11M Members

Beginning C++0x: Design of Ownership

 
8
 

Beginner's guide to C++0x:
Avoiding Memory Problems by Design of Ownership

If you ever read any resources on C++ programming or object-oriented programming, you must have heard some rethorics like "inheritance is for the is a relation, composition is for the has a relation". Is that all? Is that the end of the story? What is the underlying motivation for this dichotomy? What if my case fits neither category?

The point of this tutorial is to answer those questions and show how to translate the answers into real C++0x code. This tutorial is really about thinking about the relationships between the objects that constitute a software.

We can already answer the first question: Is that all? No. Programming tasks are rarely simple, and applications of object-oriented programming are no exception, meaning that relationships between objects can be complex, and so, no, the "is a" and "has a" dichotomy is not all there is to object-oriented design.

If you have read the title of this tutorial, you have also read the answer to the second question: What is the underlying motivation for this dichotomy? Ownership. The "is a" and "has a" dichotomy is really there to separate what specializes what and what owns what. Off the bat, of course, even these broad relations are not enough, it's missing the "unrelated" relation. In some sense, there are three poles, i.e. no-relation, specialization and ownership, that bound the continuum of possible relations between objects in a software. Needless to say, the easiest relationship is "no-relation", and that is always the preferred relation, don't create needless dependencies. Specialization is a nice topic which should be the subject of profound reflection while designing a library, and many OOP resources focus heavily on this aspect, but not this tutorial (possibly a future tutorial). We will focus on all relations that are camped in the "ownership" pole. But we start with a broad-brush note on the case when specialization is preferred, we then move to (almost) all the possible ownership relations and how to implement them in C++.

This tutorial will expose the design of a simple 3D computer game. This is a good example from which cases of all the different relationships that characterize software design can be taken and explained. However, it must be said that this will not constitute a complete or very good design for a computer game, but at least better than average.


Is a

The "is a" relation is far from simple and this section will only scratch the surface. Let's first set things straight, the "is a" relation pertains to specialization, not instantiation. What this means is that we mean "is a" as in the statement "a dog is an animal" (which means a dog is a "special" kind of animal), but not as in the statement "fido is a dog" (which means fido is one "instance" of a dog). So, the "is a" expression is really just a short for "is a special kind of".

The rule is simple. If you are designing two classes, A and B , for which you can comfortably say " A is a special kind of B ", then you should use inheritance, as in, A should be a class derived from B . In this case, B is called an abstraction because it defines what is required of a class to fulfill a particular role in a software. A red flag should always come up if you try, painfully, to harmonize a derived class with a base class you wish to derive from. If the base class requires a certain number of implementations (overridden methods) that seem to be hard to implement sensibly in the derived class, then it means that either the derived class is not really a specialization of the base class, or the base class requires too much. The latter problem can be solved by the principle of minimum requirement, which is a fundamental tenant in software design, that is, you should never impose more requirements
on the implementation of an abstraction than absolutely necessary to be able to use the abstraction in the methods it's destined to be used in. The former problem means that the "is a" relation does not hold, and inheritance is not appropriate. In other words, never force things, if it doesn't seem that a derived class can implement the base class interface correctly without "hacking" it, then don't do it.

Let's get practical. In this tutorial, we will take the example of writing a graphics computer game (skipping the details, of course). A basic element of a graphics game is, of course, visible objects. Clearly, there are many types of visible object, like trees, characters, rain, buildings, the sky, etc., which will each require special implementations. But they are all "visible objects", so it makes sense to have a base-class to represent that abstract concept. Oh, well, did we just apply the "is a" rule? No, we didn't, we've made a big mistake instead.

The "is a" rule should never be used to design the base-class. It's a classic beginner's mistake to do "derived class intersection" to design base-classes. You should never go about collecting a list of classes that are planned for and start extracting everything they all have in common and put all this into a base-class. That's dangerous, and here's why. An abstraction can be implemented in infinitely many ways, if you extract the definition of the abstraction from a finite collection of foreseen implementations, you run into the risk of leaving some unforeseen (or not yet foreseen) implementations out, to the point that these implementations cannot fit the abstraction and the "is a" relation can no longer hold in practice, even though it should in theory.

For example, for our graphics game example, if you think of "visible objects" like a character, a tree, and a car, you could easily say that all visible objects can be rendered and have a position. You then write a base-class to represent all visible objects which could look like this:

class VisibleObject {
  public:
    //virtual function to obtain the position.
    virtual Vector3D getPosition() const = 0;
    //virtual function to set the position.
    virtual void setPosition(const Vector3D& ) = 0;
    //virtual function to render the object.
    virtual void render() const = 0;
    //never forget to make the destructor of a polymorphic class virtual:
    virtual ~VisibleObject() { };
};

Later, you're coding along, and decide that you want to implement a sky for your 3D scene. Oups, what's the position of the sky?.. No.. "up-there" is not a valid Vector3D value. You are now stuck with a difficult decision:

  1. Repair: you redesign your base-class to eliminate the position requirement and thus, all the code that depends on that; or,
  2. Press-fit: you implement the Sky class by outputting some dummy value for the position and doing nothing in the setPosition function; or,
  3. Ad hoc: you implement some special code in the rendering loop to deal with this Sky class.

None of these options are desirable, and they can be avoided by the proper design method.

Let's rethink the problem the other way around. What method do you wish to implement? Easy, you want to render a bunch of stuff in the scene. What do you need for that? You need things that can be rendered. Let's design an abstraction for that:

class RenderableObject {
  public:
    //virtual function to render the object.
    virtual void render() const = 0;
    //never forget to make the destructor of 
    // a polymorphic class virtual:
    virtual ~RenderableObject() { };
};

Now, because the abstraction only requires the minimum amount of functionality required for the method, you won't have trouble fitting any class you want to use that method for into that abstraction. Once you have the abstraction, then you apply the "is a" rule and determine what should be derived from that base-class. If anything doesn't fit into the abstraction, you are sure that it is not a specialization of that base-class. Using this methodology will dramatically reduce the need for ever repairing a broken abstraction, will rule out the possibility of press-fitting a class into implementing an abstraction, and will ensure that the additional "special" implementation of the method for classes that don't fit the abstraction is necessary and will usually lead to new abstractions. To reiterate, the methodology to follow is:
[LIST=2]
\begin{enumerate}
[*] Determine the method or algorithm to implement.
[*] Determine the minimum requirements (and customization points) of the method.
[*] Group the minimum requirements, and only those minimum requirements, into cohesive abstractions.
[*] Apply the ``is a'' rule to determine what concrete implementations should implement those abstractions.
[/LIST]
This completes this brief treatment of the "is a" rule. We now move on to the issue of ownership.


Has a

The simplest and most common form of ownership is characterized by the "has a" rule. Similar to the "is a" rule, if you can comfortably say that " A has a B " then B should be a part of A . This relationship could be qualified as the strongest form of ownership, but it is most often referred to a composition because A is composed of B and other things.

One of the central design principles is: "inherited not to reuse, but to be reused" (Sutter and Alexandrescu, 2004). If you want to reuse the code of a particular class, then use that class to make-up a part of your new class, via a data member. There is nothing wrong with reusing the code of a base class in a derived class, of course, but it should never be the motivation for using inheritance. The problem with inheriting to reuse is that you are forced to hack the semantics or the interface of the base-class because you are trying to force an "is a" relation where it does make sense, and if you remember the lessons of the previous section, then you know you should never do this.

Let's get back to our example case. As already mentioned, many visible objects in a graphics game would also have an associated position, or more generally, a 3D coordinate frame (often called the body-fixed frame) that describes its position and orientation (and possibly more information, like velocity). Here, it's pretty obvious that an object like a character is not a 3D frame, but it has a 3D frame. In this case, the "has a" relation holds and it is thus most appropriate to make the 3D frame a part of our character class, as so:

class Character : public RenderableObject {
  protected: //Character is likely to be derived.
    //Make the 3D frame a part of Character:
    Frame3D coordinateFrame;
    ...
  public:
    ...
    void render() const; //override base-class function
};

This is really all there is to it. It's simple, clear and clean, and making a habit of writing RAII classes generally makes things even easier (see my last tutorial). When the ownership is as strong as this case of "composition", no memory can leak from the class in question because it contains the object. Memory cannot be corrupted because the data member is always there as long as the object of the containing class exists (you cannot refer to already deleted memory unless the memory of the containing class is already deleted too). In other words, as long as all the data members of a class don't leak memory and don't risk accessing deleted memory (through dangling references), then the class itself has the same robustness qualities, and this is how robustness propagates and why the saying goes that "C++ does not need garbage collection because it naturally does not create any garbage". This is clearly a very powerful concept and allows management of any kind of resource (including memory, of course).

However, there are many cases which don't really fit this "has a" relation the way the above example does, and the next sections will address those cases. But, note that all the techniques presented subsequently are based on the same principle as above, that is, use objects with automatic behavior (RAII) to manage memory with the strategy appropriate for each case.


Shares a

This section will consider the "shares a" case which, simply put, means that you cannot say that a particular object is owned by one object or another. More formally, the "shares a" relation applies when you have two or more objects of considerable and somewhat undetermined life-times which all require the same object, then that object is shared. This is not to be confused with other cases which are not as strong, in particular the "borrows a" and "refers to a" relations. The former refers to the case where only one object has a persistent life-time while the others are only very short-lived (temporary or local), in which case they can simply borrow as opposed to share. The latter refers to the case where an object does not require the shared object but can refer to it if it exists. A "shares a" relation is a strong bond between objects and it is generally better not to require it, as of the principle of least dependency. Furthermore, managing memory in the presence of a "shares a" relation is not free, it comes at a certain price in terms of performance and memory. Again, the traditional principle of C++ applies, you pay for what you get, but only have to pay for what you need, you just have to know what you want.

Continuing with our computer game example, we will now look at the resources needed by our renderable objects. Most objects to be drawn on the scene will have a 3D model that represents their shape (probably as a mesh of polygon primitives). Now, we could apply our previous rule, the "has a" rule, because, after all, each visible object has a 3D model. The problem with this approach is that if there are several objects with the same model (e.g. trees, characters, cars, etc.), then this strategy will force the same 3D model to be loaded into memory and built into the 3D graphics library several times, again and again. To avoid this waste, it is more sensible to say that several visible objects share the same 3D model. We will then be able to load the model once into memory and/or the graphics card, and have each visible object reuse it.

The characteristics described in the introductory paragraph apply to this problem, that is, the visible objects have a long life (as long as they are needed in the scene), their destruction and order of destruction cannot be predicted (especially if controlled by the user or AI), and they all require the existence of the 3D model in order to be renderable to the scene. But how do we turn the "shares a" relation into C++ code?

C++0x provides a number of smart-pointers, which are, as their name implies, pointer-like objects that have some logic or intelligence attached to them, usually in the form of the automatic behavior they have. They are all RAII classes that wrap a pointer, and in that sense, they are applications of the very powerful and easy to use technique of composition (or direct ownership) of value-objects (as described in the previous section) to serve special purposes. All these smart-pointers and other memory related functions and classes are found in the standard header <memory> .

Because of the characteristics of a "shares a" relation, it is required to keep track of the objects which are still using the shared object, making sure that the shared object is not deleted while one of its owners still needs it and that the shared object gets deleted as soon as none of the owners still exist or still need it. One way to achieve this is to have some almighty entity that watches everything and accomplishes these objectives, this is called a garbage collector. Another solution is to simply attach a reference count to the shared memory (or object, or resource), where each smart-pointer that is created to point to that memory increments the reference counter and every destruction of such a pointer decrements it, and thus, the objectives can be accomplished by simply deleting the memory or object once the reference count goes to zero. This behavior is implemented in the std::shared_ptr class template. Of course, there is some performance penalty associated to this smart-pointer, because it is slightly more expensive to copy than an ordinary pointer, but the price is well worth it. As long as the programmers are disciplined and never keep persistent references to the memory, that is held by a shared_ptr , with some raw-pointer
or reference. Since smart-pointers are design to mimic a raw-pointer when it comes to using them (much like STL iterators), it is not difficult to keep this discipline.

Coming back to our 3D model example, we can now add a shared_ptr to a 3D model for our Character class. We simply write:

class Character : public RenderableObject {
  protected: //Character is likely to be derived.
    //Make the 3D frame a part of Character:
    Frame3D coordinateFrame;
    //Hold a shared_ptr to a 3D model object:
    std::shared_ptr<Model3D> model;
    ...
  public:
    ...
    void render() const; //override base-class function
};

Factory Function Pattern

We can now allow several characters to have the same 3D model without the trouble of having to determine when it's appropriate to delete that model without the risk of breaking one of the characters. But, one issue remains, the most dangerous place where the discipline of always using a shared_ptr can be broken is where the object gets created. In theory, you could develop the discipline to always create objects and immediately wrap the pointer into the shared_ptr , as so:

std::shared_ptr<Model3D> model( new Model3D(/*...*/) );

However, such discipline is annoying and ugly at the site of creation. A very useful trick to solve this problem is to use the factory function pattern, and new features of C++0x make that pattern even more applicable. The basic pattern is simple, say we can construct our 3D model from the file-name of the model to load, then we can have:

class Model3D {
  private:
    ...
  public:
    //Have a constructor from a file-name:
    Model3D(const std::string& aFilename);
    ...
    //Have a static factory function for the models:
    static std::shared_ptr<Model3D> Create(const std::string& aFilename) {
      return std::shared_ptr<Model3D>(new Model3D(aFilename));
    };
};

The problem with the above code is that it does not scale to the addition of more constructors. Many classes typically have several constructors, such as a default constructor (no parameter), a copy constructor and possibly different overloaded constructors, and thus, you would have to provide an overloaded factory function for each of those cases. Fortunately, C++0x introduces perfect forwarding (via rvalue-references) and type-safe variable-length parameter list (via variadic templates). To start, we could achieve perfect forwarding of one argument in our factory function as so:

class Model3D {
  ...
    //Have a static factory function for the models:
    template <typename T>
    static std::shared_ptr<Model3D> Create(T&& arg) {
      return std::shared_ptr<Model3D>(
               new Model3D(std::forward<T>(arg)));
    };
};

Refer to C++0x literature for more details on perfect forwarding and how the above works. Now, the addition of variadic templates allows you to write one factory function for all the constructor overloads, that's awesome:

class Model3D {
  ...
    //Have a static factory function for the models:
    template <typename... Args>
    static std::shared_ptr<Model3D> Create(Args&&... args) {
      return std::shared_ptr<Model3D>( new Model3D(std::forward<Args>(args)...) );
    };
};

Note that the ... is not accidental and does not stand for anything, it is the ellipsis operator that is used to form and use variadic templates, read C++0x literature for more details.

With the factory function pattern, we can now wrap all the creations of the objects within a function that directly wrap the new pointer into a smart-pointer. This seals the first dangerous point when using smart-pointers, and the remaining pitfalls are few and far between. An additional use of factory functions is to provide a function that can generate any kind of polymorphic object from a given parameter. In such case, and for our 3D model example, one could do something like this:

std::shared_ptr<Model3D> LoadModel(const std::string& aFilename) {
  //call some function to get the file-extension:
  std::string model_type = get_extension(aFilename); 
  if( model_type == "3ds" ) 
    return std::shared_ptr<Model3D>(new Model3DS(aFilename));
  else if( model_type == "ms3d" )
    return std::shared_ptr<Model3D>(new ModelMS3D(aFilename));
  //.. et cetera for other file-formats.
};

Another problem that can come up when using smart-pointers is that you may somethings have to interface objects that are not wrapped in a smart-pointer with code that requires or assumes it, and vice-versa. If you follow the coming guidelines in this tutorial, then these situations should be very rare. But if they do, you simply have to be more careful and understand what you are doing. One trick, however, can be handy if you need to create a shared_ptr to point to an object that is either a local object, a data member, or held by some other kind of (smart-)pointer. First, you need to be careful and make sure that the object will exist as long as there is a shared_ptr pointing to it. Then, you can create a shared_ptr that has a null_deleter , meaning that the "delete" operation
that the destruction of the last shared_ptr will trigger will not actually do anything at all. A deleter, as used for shared_ptr (can be passed as a second parameter to the construction of the shared_ptr ), is simply a callable object (or functor) which takes the pointer to be deleted as a parameter, and since all pointers are implicitly convertible to a const-void pointer, you can create a null-deleter as so:

struct null_deleter
{
    void operator()(void const *) const
    { //do nothing!
    }
};

This will allow you to safely create code like this:

Model3D my_model("some_model.3ds");
  std::shared_ptr<Model3D> my_model_ptr( &my_model,
                                         null_deleter());
  //.. then use this shared_ptr with classes or functions 
  //    that require it.

The reason why the standard library does not provide such a functor ( null_deleter ) is because it breaks the automatic behavior and purpose of the shared_ptr class, and should never be used under ideal circumstances, but the real-world is rarely ideal and this trick can be useful from time to time.

This pretty much completes our treatment of the "shares a" relation, but the acute reader might notice that there are problems with the shared_ptr class. First, we need, of course, a means to setup the sharing of the object, which does require some overseeing entity in the code to make the connections between objects that share something (e.g. we need a central entity to provide already loaded 3D models to the visible objects, if the requested model is already loaded). And second, there can be problems of cyclic dependencies between shared objects. The cycle problem is well known and is an issue to be aware of. This issue comes up when you have several objects which hold shared_ptr s to each other, to the point that it makes a closed loop (or cycle) of references. The problem is that such a cycle will lock the object structure because none of the objects involved in the cycle can ever be deleted because they all need each other (i.e. no object can be deleted without breaking an object it depends on through the cycle). The solution to both problems involves another smart-pointer that is the natural companion of shared_ptr , and is called weak_ptr and marks the "refers to a" relation, which comes in the next section.


Refers to a

When an object is shared amongst different objects, all objects may not actually need the shared object, and in this case, we say that that object refers to the shared object for some purpose. In order to mark this relationship in C++, we can use the weak_ptr class. This class is a companion of the shared_ptr class because it is made to interact strongly with it. The idea is simple, knowing that a shared_ptr keeps a reference count of the current uses of an object and accomplishes shared ownership this way, a weak_ptr is linked to the same reference count but does not share the ownership with the shared_ptr objects. Instead, the weak_ptr simply watches the reference count and if it goes to zero, the weak_ptr becomes expired. To actually use that object that a weak_ptr points to, one has to first create a shared_ptr using the lock() function of the weak_ptr . Because the weak_ptr is not part of the reference count, it does not prevent the destruction of the object, but can be used to get ownership of the object if it still exists (not expired). In other words, the typical code that uses a weak_ptr looks like this:

void myFunction(std::weak_ptr<double> value_wp) {
  //check if the pointer is expired:
  if( ! value_wp.expired() ) {
    //lock the pointer to obtain a shared_ptr:
    std::shared_ptr<double> value_sp( value_wp.lock() );
    //use the shared_ptr:
    *value_sp = 42.0;
  };
};

So, you might ask, why do we need the weak_ptr class? Because it solves the two problems with the shared_ptr class. First, it can break cyclic dependency problems. You can never really need a cyclic dependency, you will always find that at least one of the links in the cycle does not have to be a strong dependency but rather a weak dependency, and thus, can be realized with a weak_ptr .

For example, in our game programming example, we might want to link our character object with a controller of some kind (i.e. an object that controls the motion of the character, like a human controller or AI). Then, obviously, the controller will need the character it controls, otherwise it has no purpose. Also, the character might use some information from its controller too. But the character might not have a controller (stay put) and does not strictly need it, but can refer to it. In this case, we can do:

class CharacterController; //forward-declare.

class Character : public RenderableObject {
  protected: //Character is likely to be derived.
    //Make the 3D frame a part of Character:
    Frame3D coordinateFrame;
    //Hold a shared_ptr to a 3D model object:
    std::shared_ptr<Model3D> model;
    //Hold a weak_ptr to the controller:
    std::weak_ptr<CharacterController> ctrl;
    ...
  public:
    ...
    void render() const; //override base-class function
};

class CharacterController {
  protected:
    //Hold a shared_ptr to the subject:
    std::shared_ptr<Character> subject;
    ...
  public:
    ....
};

If we were to implement the above using a shared_ptr in both cases, we would have a cyclic dependency, but now we don't. Also not that the rules for incomplete classes (when only forward-declared) work the same for any smart-pointer as they do for normal pointers, so it is possible to declare a smart-pointer to an incomplete type, just like it is possible to declare a normal pointer to an incomplete type.

The second interesting problem that the weak_ptr class solves is the implementation of the overseeing entity that establishes the shared-ownerships of objects. Such an entity is usually called a proxy or broker, and is very often implemented as a singleton. This tutorial will not expose the design of a singleton class but will mention how to design a resource broker. Getting back to our 3D model problem, in order to make sure we don't load the same model twice, we need a resource broker that will keep track of all the 3D models that have been loaded by our computer game. However, we don't want the broker to own the 3D models, because we want the life-time of the 3D models to be tied to its usage, not to the broker (which is permanent in the software). We can easily tie the life-time of the 3D model to its usage by using the shared_ptr class and its reference-counting feature. However, we still want the broker to be able to access the loaded 3D models without having to keep track of usage. The weak_ptr class provides the perfect solution for this problem. We can then write:

class Model3DBroker {
  private:
    std::map< std::string, std::weak_ptr<Model3D> > models;
  public:
    std::shared_ptr<Model3D> 
     getModel(const std::string& aFilename) {
      //find the model, if already loaded at one point.
      // if never loaded, the map will create a fresh one.
      std::weak_ptr<Model3D>& wp = models[aFilename];
      //Check if the model is expired or new.
      if( wp.expired() ) {
        //Load the model into a shared_ptr.
        std::shared_ptr<Model3D> sp = LoadModel(aFilename);
        //Link the weak_ptr to the loaded model.
        wp = sp; 
        //Return the loaded model.
        return sp;
      } else 
        return wp.lock(); //Return the loaded model.
    };
};

We can see that the above achieve its purpose and is incredibly simple. The only overhead is that it will keep the list of all the expired weak-pointers, but this can easily be remedied with an occasional cleaning pass that removes all the expired entries from the map. Resource brokers are notoriously difficult to implement correctly, but using C++'s smart-pointers, it's a piece of cake. Resource brokers apply to almost any resource objects that can be found in a software, in particular to a computer game, you can apply this principle to managing models, textures, audio files, animations, etc.

Finally, another interesting use of the weak_ptr class is when you need an object to be a broker for itself. If you need an object to be able to provide a shared_ptr to itself, it can obviously not hold a shared_ptr to itself because that would be an obvious cyclic dependency. To solve this problem, there is no perfect solution. The first solution is to hold a weak_ptr that is linked to a shared_ptr that owns the object. But this requires initializing that weak_ptr after the construction of the object and it requires that the object always be created and wrapped into a shared_ptr . Thus, the solution involves two things, adding the initialization of the weak_ptr to the factory function and disabling construction by any other means then by the factory. We thus have:

class Model3D {
  protected:
    std::weak_ptr<Model3D> wThis;

    Model3D();  //protected default constructor.
    Model3D(const std::string& aFilename); //protected again.
    ...
  public:
    //Have a get function for the weak-pointer:
    std::weak_ptr<Model3D> getWeakPtr() const {
      return wThis;
    };
    //Have a static factory function for the models:
    template <typename... Args>
    static std::shared_ptr<Model3D> Create(Args&&... args) {
      //create object (constructors are accessible here only):
      std::shared_ptr<Model3D> result( new Model3D(std::forward<Args>(args)...) );
      //initialize the weak pointer:
      result->wThis = result;
      return result;
    };
    ...
};

The above is nice because it allows the model object to be able to provide a shared_ptr that owns it. However, the draw-back is that the object can only be created with the factory function, and most importantly, it can only be dynamically allocated (not as a local object or data member). Nevertheless, this is a solution that is appropriate for many cases.

The second solution is to use the same trick as before, using the null-deleter. In this case, we can do the following:

class Model3D {
  protected:
    std::shared_ptr<Model3D> sThis;

  public:
    Model3D() : sThis(this, null_deleter()) { };
    ...
    //Have a get function for the weak-pointer:
    std::weak_ptr<Model3D> getWeakPtr() const {
      return sThis;
    };
    ...
};

The above solutions solves the problems of the previous solution, however it is not as safe. It's not very safe because the shared_ptr that is obtained from the weak pointer that the class gives does not have ownership of the object. So, this weak-pointer can only really serve for checking if the object has expired, and getting a shared_ptr to it for very short-term use (like locally to a function), this is often reasonable but not "air-tight". Choosing between either solutions can be difficult, and of course, the preferred solution is to not make the object a broker for itself, but, again, the real-world is rarely an ideal place.


Creates a

There is an issue that was not discussed in the "has a" section, that is, the case where the composition involves a polymorphic class. Polymorphism (in OOP) can only be achieved through pointers or references, and thus, in order to hold a polymorphic object, a pointer is required. If the "has a" rule applies, but the object in question should be polymorphic, then a unique_ptr should be used. The unique_ptr class is another smart-pointer class introduced by the new C++ standard, also part of the header <memory> . This smart-pointer implements unique ownership and automatic behavior, in other words, the pointer cannot be copied (but can be moved) and will automatically delete the object it points
to when it is itself destroyed. This new smart-pointer replaces the deprecated auto_ptr class which had similar characteristics but no move-semantics, making it of very little useful purpose.

For arguments similar to those that apply to the shared_ptr , it is greatly preferred to create objects within a factory function that directly wraps the object into a unique_ptr . However, the non-copyable characteristic of the unique_ptr class requires a slight modification to the factory function. In order to carry the unique_ptr out of the factory function, move-semantics must be used, and that is done by returning an rvalue-reference from the factory function. So, we can revisit our factory function for the Model3D class as so:

std::unique_ptr<Model3D>&&   //return an rvalue-ref.
 LoadModel(const std::string& aFilename) {
  //call some function to get the file-extension:
  std::string model_type = get_extension(aFilename); 
  if( model_type == "3ds" ) 
    return std::unique_ptr<Model3D>(new Model3DS(aFilename));
  else if( model_type == "ms3d" )
    return std::unique_ptr<Model3D>(new ModelMS3D(aFilename));
  //.. et cetera for other file-formats.
};

Or, the static member function as the factory function:

class Model3D {
  ...
    //Have a static factory function for the models:
    template <typename... Args>
    static std::unique_ptr<Model3D>&& Create(Args&&... args) {
      return std::unique_ptr<Model3D>(
               new Model3D(std::forward<Args>(args)...));
    };
};

The above factory functions work because returning a temporary object from the functions implicitly bound them to the rvalue-references required as a return-type. However, if you want a more complex factory function that must store the unique_ptr as a local variable for some time before returning it, then the std::move function is required to explicitly move the unique_ptr into the returned rvalue-reference, as so:

class Model3D {
  ...
  public:
    //Have a static factory function for the models:
    template <typename... Args>
    static std::unique_ptr<Model3D>&& Create(Args&&... args) {
      std::unique_ptr<Model3D> result(
               new Model3D(std::forward<Args>(args)...));
      //... do something with the object.
      // then return it:
      return std::move(result);
    };
};

Now, you may ask, do I have to create a factory function for both cases, one for shared_ptr and one for unique_ptr ? No. Since the unique_ptr class implements unique ownership, it is safe to move its pointer into a shared_ptr object, and thus, the standard provides an implicit move-conversion from a unique_ptr to a shared_ptr . In other words, all you need to provide is a factory function that returns a unique_ptr and its result will implicitly convert to a shared_ptr if that is the desire of the caller of the factory.

To add an example for our computer game scenario, we can take the example of textures for the 3D models. We will assume that each 3D model has a unique texture, which is usually not the case and the textures would usually be handled the same way the 3D models were (with resource broker). Since there are many different file formats for images and many different representations of the images (pixel formats), so we need different polymorphic classes to load and use them, which we can construct with a factory function that is similar to our LoadModel function (but returning a unique_ptr rvalue-ref instead). So, our model class could have the following addition:

class Model3D {
  protected:
    std::unique_ptr<Texture> texImage;
  ...
  public:
    void loadTexture(const std::string& aFilename) {
      texImage = LoadTexture(aFilename);
    };
  ...
};

std::unique_ptr<Texture>&& LoadTexture(const std::string& aFilename) {
  //call some function to get the file-extension:
  std::string image_type = get_extension(aFilename); 
  if( image_type == "bmp" ) 
    return std::unique_ptr<Texture>(new TextureBMP(aFilename));
  else if( image_type == "tga" )
    return std::unique_ptr<Texture>(new TextureTGA(aFilename));
  //.. et cetera for other file-formats.
};

Borrows a

Last but not least, we tackle the "borrows a" case. So far, we have looked as when you use direct ownership with value-objects and when you use all the different standard smart-pointers, but we have been ignoring the two elephants in the room: references and (raw-)pointers. It could be said that normal pointers are called "raw-pointers" because, like raw-meat, they can transmit mad-cow disease and other ugly things that will mess with your mind. References are much nicer because they are less movable than pointers, and that fits the "borrows a" relation very well. Let's look closer at how references work.

References have great locality because they are initialized (from a variable) by grabbing the address of the variable and they cannot be re-seated after that. In other words, the reference cannot exist before the variable it refers to exists, unlike a pointer that can be created at one point and assigned an address to a variable later on. Because references are not copyable (the reference itself cannot be copied) or transferable, they implement a temporary borrowing of a variable or object, one that cannot (easily) survive beyond the life-time of the variable or object they refer to. Nevertheless, you do have to watch out for dangling references and not abuse references for situations where the reference is meant to exist too persistently (beyond a local scope).

The most typical use of the "borrows a" relation is as a function parameter. In other words, when you have a function that needs to borrow an object from the caller either to use it without needing a local copy (i.e. by const reference) or to modify it as an output of the function (i.e. by non-const reference), then you should pass that object by reference to the function. This is always safe because the caller's object will certainly survive for the time of the function call and since reference are very hard to copy, the convention is that parameters taken by reference to a function will not be kept (as reference or pointer) beyond the execution of the function. For example, in our game-example, we could have an Animator class which could store some sort of motion to apply to a character. In this case, we could have a function like this:

class Animator {
  ...
  public:
    void applyTo(Character& aChar) {
      .... apply some motion to aChar ...
    };
  ...
};

The above implements a "borrows a" relation because the Animator class borrows a character on which to apply its animation (maybe for one time-step or something).

By convention, in C++, references are always taken to be a temporary borrowing of a variable or object, and this applies also to classes that can store references. Sometimes, you might want to create an object that borrows another object, and, in that case, you want the same "non-copyable" property to hold for your object. Typical use-cases for this are what I call algorithmic classes (and algorithm-factories). An algorithmic class is a class which is used just to set-up and execute an algorithm. Very often, such classes could and should be implemented as free-functions (most code that is algorithmic in nature should be implemented as free-functions), however, it is sometimes desirable to provide a class for that algorithm. The reasons for doing this might include being able to name parameters (as opposed to a free-function with a large number of unnamed parameters) and also being able to have default values for any parameters (not just the last parameters of a function). Very often, such classes have a certain number of necessary parameters and often, it needs to borrow those objects. This can be realized by holding a reference in the class and thus, it must be created with the required borrowed object and then it is a non-copyable and local class.

For example, if we wanted to create some dynamic simulation (physics engine) for some objects in our computer game, then we will assume that this dynamic system is implemented via some abstract DynamicSystem class. Now, during an interation of the program's loop, we might want to integrate the dynamic equations (i.e. equations of motion) for some time interval (e.g. the real-time interval between interations of the program). We can do that with a numerical integrator, which is a method to simply add small changes in the state-vector (given by the equations of motion) for very small increases in time (time-steps) until the end of the interval is reached. The simplest such method is the Euler-method, and of course, it is an algorithm that should be implemented as a free-function, but it can also be wrapped in an algorithmic class. So, we could implement this class as so:

class EulerIntegrator {
  private:
    DynamicSystem& sys; //hold a reference.

    double currentTime;
    double timeStep;
    Vector currentState;
  public:
    //Constructor with required params:
    EulerIntegrator(DynamicSystem& aSys) : 
                    sys(aSys),  //set the ref.
                    currentTime(0.0), //give defaults here.
                    timeStep(1E-4), 
                    currentState() { };
    // Then provide set-functions:
    EulerIntegrator& setCurrentTime(double aCurrentTime);
    EulerIntegrator& setTimeStep(double aTimeStep);
    EulerIntegrator& setCurrentState(const Vector& aState);
    
    //Then provide a execute functions:
    void integrate(double aEndTime, Vector& aFinalState) {
      .... implement the integration from currentTime to 
      .... aEndTime, storing the final state-vector in 
      .... aFinalState.
      .... NOTE: you can also simply call a free-function
      ....  here with all the parameters.
    };
  ...
};

Notice, in the above, that the set-functions return a non-const reference to the object itself. This idiom, called the Named Parameter Idiom, is very useful for such temporary objects and algorithmic classes, because they allow the above class to be used like this:

void myFunction() {
  ...
  DynamicSystem my_system;
  ... configure the system.

  //create a algorithmic-object (temporary):
  EulerIntegrator(my_system)        //link to my_system
  .setTimeStep(1e-3)                //set time-step only
  .setCurrentState(my_init_state)   //set initial-state.
  .integrate(10.0, my_final_state); //execute.

};

You can see that the above is nice and clean, and safe. By convention, classes that take non-const references are expected to be locally created only, and facilitating the creation and use of temporary objects is even better. If you need to take const references (for const-correctness), than you often need to disable overloads that might be taking an rvalue (a temporary). In old C++, you cannot do this, but in C++0x, you can by using both the feature of rvalue-references (which bind to temporaries with higher priority than const-references) and the explicit deletion of function overloads, as follows:

class EulerIntegrator {
  private:
    const DynamicSystem& sys; //hold a const-ref.
   ....
  public:
    //Constructor with required params:
    EulerIntegrator(const DynamicSystem& aSys) : 
                    sys(aSys),
                    .... { };
    //Disable construction with rvalue:
    EulerIntegrator(DynamicSystem&& ) = delete;
   ....
};

Inversion of Control and Fly-weight Pattern

Sometimes, there are too much borrowing in a software. Software should, in general, use borrowing a lot because it allows customizations via polymorphism (like the integrator example above which integrates any kind dynamic-system by borrowing a base-class object reference), and that is a good design pattern (reusability). However, sometimes having a lot of borrowing leads to code-bloat with lots of reference parameters and data-members, lots of call-back function pointers, and also many "configuration" parameters to functions or classes. To solve these issues, I refer you to two main design patterns that are very useful, that is, Inversion of Control and Fly-weight Pattern. In a nutshell, the Inversion of Control (IoC) pattern is based on the idea of inverting the handling of the control-flow of an algorithm or method, that is, instead of having the manager class handle the execution of an algorithm using some underlying helper functions, it inverts that pattern by allowing some generic algorithm to handle the control-flow and use (borrowing) the manager class to inject custom code at key points in the algorithm. And the Fly-weight Pattern is a simple pattern where a lot of parameters are lumped into one (persistent) object that is carried through the function-calls. There is a lot of literature on these patterns and google is your friend.


Conclusion

This tutorial has looked at design from the point of view of ownership relations between components of a software (OOP or other) in C++. These design guidelines will not only produce a software that is much more robust to memory issues, it will also lead to a better design methodology and a better software design will result from that.

A final issue to discuss is the unpredictability of a software design. You cannot always anticipate all the features or additions to a software or library, and thus, you might not always be able to anticipate the most appropriate ownership relation between the objects. The most versatile solution, but also the most expensive, is the use of shared_ptr because it can work for any of the ownership relations, but it might be inefficient when it is not needed. So, when in doubt, use shared_ptr .

Another thing one might have noticed from this tutorial is that none of the design patterns presented here make any use of raw-pointers. That's not a coincidence. Raw-pointers have very limited uses in C++ and when they are used, they should be used for expert-level designs and encapsulated within a class (a RAII class) or within the implementation of a function, but they should never appear in an interface, because raw-pointers do not convey or implement any kind of ownership semantics, and thus, they are not safe and ambiguous if they appear in an interface. In other words, when should you use raw-pointers? Don't, at least not until you have significant experience in C++. Of course, don't take my word for it, think for yourself!

Member Avatar
Mikael Persson

Career objective:
To lead research and development of robotics applications for consumers, industrials or the aerospace industry; to best utilize multidisciplinary competences within a research or development team; and to help use robotics and mechatronics technologies to bring a new developmental revolution.

Expert Programming Skills: With more than 12 years experience in object-oriented programming and softwarengineering, skills range from low-level hardware programming (device drivers, buses, etc.) to high-level programming (artificial intelligence, event-driven software, etc.). Main expertise is in C/C++ with active participation to online C++ forums and have occasionally written programming tutorials and done code reviews. Very competent in many more languages including MatLab/Simulink, Delphi, LabVIEW, Java, etc.

Mechanical Engineering Skills: Competent with most CAD software (AutoCAD, Pro/E, SolidWork, etc.) as well as many simulation software (MSC - ADAMS / PATRAN / NASTRAN, SimuLink, Ansys, etc.). Skilled with mechatronics designs including design and analysis of mechanism, design optimization, electrical drives, and various actuator technologies. Expert analytical skills and numerical simulation skills for multi-body dynamics.

Control Engineering Skills: Competent and experienced with either analysis, synthesis, or implementation of control systems and software. Familiar with robust control theory, discrete-time issues, non-linear control, and probabilistic approaches to estimation and control. Strong mechatronics analytical skills for model-based control approaches and implementations.

Electrical Engineering Skills: Familiar with printed-circuit board design, circuit analysis and various low-level communication buses. Skilled with electrical drives and control circuits. Most familiar with common sensors used in robotics (lidars, computer-vision, INS, etc.).

Operating Systems: Linux, Windows, and any Unix-based system.

Other Applications: MatLab/SimuLink, LaTeX, MS Office Suite, LabVIEW, MSC ADAMS, Ansys, SolidWork, etc.

Languages: French and Swedish as mother tongues; Fluent in English, written and spoken, at mother tongue level; Functional in German, read and spoken; Basic skills in Spanish and Finnish.

Citizenship: Dual-citizenship as Canadian and Swedish.

Miscellaneous: Skilled at machining with most common machine tools, strong verbal and written communication skills, excellent troubleshooting and debugging skills, exceptional problem solving skills, and work well in a team.

 
0
 

Very nice, learned quite a bit and haven't even finished it. Have to come back to finish it at a decent hour. Thumbs up.

 
0
 

Can you also post a link to your last C++0x tutorial (about RAII Class)? I haven't finished reading it and I lost the link :(

#EDIT#
Never mind, I found it.

Isn't it about time forums rewarded their contributors?

Earn rewards points for helping others. Gain kudos. Cash out. Get better answers yourself.

It's as simple as contributing editorial or replying to discussions labeled or OP Kudos

You
This is an OP Kudos discussion and contributors may be rewarded
Post:
Start New Discussion
View similar articles that have also been tagged: