ClanLib

Using ClanLib's smart pointers

ClanLib has a set of smart pointer template classes which it uses internally, and which you can also use in your own game code. They make it easier to manage memory in cases where you don't want to use the stack due to a need for polymorphism, shared data, implementation-pointers, or lazy copying.

Smart pointers can be used as members of classes and work fine with compiler-supplied destructors, default constructors, copy constructors, and copy assignment operators. They can also be used as function temporaries, they safely handle data deletion when they go out of scope, and they can also be used inside STL containers.

CL_SharedPtr : Sharing Pointer

The simplest smart pointer is CL_SharedPtr. CL_SharedPtr will share the same data among one or more instances of the pointer:

When all the CL_SharedPtrs pointing to a particular piece of data are gone or not pointing to it anymore, the data is automatically destroyed. Keep in mind that CL_SharedPtr uses a simple reference counting system; circular loops will result in memory leaks, since the data won't ever be destroyed. However, situations where such circular loops arise are pretty rare and convoluted.

Like a regular pointer, a CL_SharedPtr can also point to derivations of its supplied type. But make sure that you give the classes you plan to do this with virtual destructors. Assuming MaBob is a derivation of Thing:

Preparing classes for use in CL_LazyCopyPtr and CL_OwningPtr

To make your classes usable in the other two ClanLib smart pointer types, it's important that the classes you use in the smart pointers descend (directly or indirectly) from CL_Clonable, and implement a very specific clone() method at the start of their definitions. For example, if you have a class called Thing, and intended to use it in a smart pointer, it would look like so:

If you want to derive from Thing, then each child class must reimplement clone(), substituting their own class name for Thing:

It's important that you reimplement clone() in every derivation of a CL_Clonable class, otherwise slicing will occur. The compiler won't warn you of this, it'll just happen, so watch out!

CL_OwningPtr : Possessive Pointer

CL_OwningPtr acts like a regular stack instance of a class: whenever CL_OwningPtr is copied, then the pointed-to class is copied as well:

In this example, possessive a and b both refer to entirely different instances of Thing, which you can access by dereferencing the respective pointer. The instance of Thing pointed to by b was made by calling the copy constructor on the Thing instance pointed to by b. When the possessive pointers are destroyed, then the things they point to are automatically destroyed as well:

CL_LazyCopyPtr : Copy-on-write Pointer

CL_LazyCopyPtr has the same usage semantics as CL_OwningPtr; you can replace the text CL_OwningPtr with CL_LazyCopyPtr in your code and everything will continue to work. The difference is that copying a CL_OwningPtr results in an immediate copy of the pointed-to data, while copying a CL_LazyCopyPtr will not result in a copy of the pointed-to data until the CL_LazyCopyPtr is dereferenced in a non-const way:

If the CL_LazyCopyPtr is a member of a class and the using code is a const method of that class, or if the CL_LazyCopyPtr is being passed to a function by const reference, then no copy of the data is made, since it's impossible for you to call a non-const method of Thing in those situations. However, in a case where you would be allowed to call a non-const method of Thing, the copy will still be made, even if you're only calling a const method. To avoid this, you can call the cd() method of CL_LazyCopyPtr, which stands for 'const dereference':

Ownership Transfer

It's important to remember that when you assign something to any ClanLib smart pointer, then the smart pointer gets control over that data. From that point, you shouldn't attempt to access the data except directly through the smart pointer:

The insidious thing about the above code is that it might work or it might segfault, depending on what happens at the 'Stuff happens here' comment.

Autocasting

When using a smart pointer as a pointer-to-implementation, it's often useful to have the pointer automatically cast dereferences to some type that has only been forward declared. For example, let's say you've got a CL_OwningPtr in your class Narf, with an implementation class called NarfImpl. Inside Narf's header, NarfImpl has only been forward declared. Since NarfImpl has to be derived from CL_Clonable to be in a CL_OwningPtr anyways, it's convienent to use that as the template parameter to CL_OwningPtr:

This works, but there's an annoying problem. In Narf's implementation file, you're forced to cast to NarfImpl* or NarfImpl& evrey time you want to call any of NarfImpl's methods. Although you know that pimpl is under your strict control and will never have any derivative of CL_Clonable besides NarfImpl in it, C++ isn't convinced and requires you do all that casting.

However, ClanLib's smart pointers can automatically do this casting for you, when you specify a second template parameter as the cast-to type:

Now, every time you dereference, do an indirect member access, or call get() on pimpl, the result will be a NarfImpl& or a NarfImpl* instead of a CL_Clonable& or CL_Clonable*.

Questions or comments, write to the ClanLib mailing list.