[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3.2 Correctly Using Smart Pointers

Written by Jorrit Tyberghein, jorrit.tyberghein@gmail.com. Updated and expanded significantly by Eric Sunshine, sunshine@sunshineco.com.

Smart pointers were introduced in Crystal Space stable version 0.96 (developmental version 0.95). The purpose of smart pointers is to make it easier to manage reference counting. Instead of manually calling IncRef() and DecRef() on an object, you use a smart pointer (csRef<>) and let it manage the reference count automatically. The csRef<> smart pointer template works with any class which provides IncRef() and DecRef() methods, such as `csRefCount', as well as all SCF classes. The csRef<> template is defined in `CS/include/csutil/ref.h'.

How to Use Smart Pointers

This is easy. For example, often you want to keep track of a few common objects in your main class (such as the pointer to the engine and so on). To do this you just declare in your main class:

 
class MyClass
{
  csRef<iEngine> engine;
  ...
}

Smart pointers do not need to be initialized, so there is no need to write `engine = 0' in the constructor of your class. There is also no need to clean them up. The reference to the engine, in the example, will be cleaned up automatically when the instance of MyClass is destroyed.

Here is an example of assigning a valid object to the `engine' smart pointer and then utilizing that pointer (assuming the engine plugin is loaded):

 
iObjectRegistry* object_reg = ...;
engine = scfQueryInterface<iEngine> (object_reg);
engine->CreateSector ();

That is all there is to it. For contrast, here is an example of how the same operations would have been performed before the introduction of smart pointers:

 
class MyClass
{
  iEngine* engine;
  ...
}

MyClass::MyClass ()
{
  engine = 0;
}

MyClass::~MyClass ()
{
  if (engine != 0)
    engine->DecRef ();
}

...
engine = csQueryRegistry<iEngine> (object_reg);
engine->CreateSector (); ...
...

The advantage might not seem huge but, in general, it is a lot easier to use smart pointers than to manually manipulate reference counts. The nice thing about smart pointers is that you can use them exactly like you would use a normal pointer (i.e. you can do things like engine->CreateSector()).

Here is another example illustrating smart pointers:

 
csRef<iMeshWrapper> sprite(engine->CreateMeshWrapper(...));
csRef<iSprite3DState> state (scfQueryInterface<iSprite3DState> (
    sprite->GetMeshObject ()));
state->SetAction ("default");

Transitioning between Smart Pointers and Normal Pointers

Smart pointers can be assigned to normal pointers and vice versa. However, you need to be aware of the interactions with reference counting.

Assigning a smart pointer to a normal pointer does not change the reference count of the object. This can be beneficial as the reference counting overhead is avoided. It can also be dangerous, as the normal pointer will become dangling when the referenced object is destroyed! This means that storing in a normal pointer is suitable for short-term storage, when it's certain that the referenced object won't be released durign the lifetime of the normal pointer. If that's not guaranteed use a csRef<> instead.

Assigning a normal pointer to a smart pointer does change the reference count: ownership is taken, ie the reference count is increased. This can cause a leaked reference in certain circumstances. One notable instance is the assignment of an object just allocated with `new' to a smart pointer: the object will start with a reference count of 1, but the assignment to the smart pointer will also increase the reference count, which errorneously becomes 2. The last "phantom" reference will never be release, causing a leak.

The recommend way to deal with that is to never assign an object allocated with `new' to a smart pointer directly. Instead use it's AttachNew() method which will take care of the proper reference counting.

When to Use csRef<>?

Use csRef<> wherever you need to own a reference to a reference-counted object. The above examples illustrate a few cases in which csRef<> is warranted. If you do not need to own a reference to a reference-counted object, and you know positively that the object will exist for the duration of a piece of code, then you can use a plain pointer rather than a smart pointer. However, for complete safety, using csRef<> will ensure that a reference-counted object will remain valid for as long as the csRef<> remains alive. For this reason, it is often simplest to utilize csRef<> when dealing with reference-counted objects, rather than plain pointers.

csRef<> is also a very handy and 100% safe mechanism for transferring object ownership from a function back to its caller. This is useful in cases when the caller of a function wants to take ownership of a brand new object created by the function on the caller's behalf. For example:

 
csRef<iFoo> MyFunction ()
{
  csRef<iFoo> foo;
  foo.AttachNew(new Foo(...));
  foo->FooMethod(...);
  ...
  return foo;
}

The reason that this is 100% safe is that the newly created object is correctly destroyed (not leaked) even if the caller of MyFunction() forgets or neglects to assign it to a variable.

When to Use csWeakRef<>?

Another smart pointer class is csWeakRef<>. The significant difference to csRef<> is that a "weak" reference is not owning. However, unlike raw pointers, csWeakRef<> prevents the risk of a "dangling pointer" (a pointer referencing a freed, thus invalid, object): when the referenced object gets destroyed, all weak references to it are automatically set to null.

A common application of weak references is the prevention of circular references. When two objects reference each other with csRef<>s neither will be destructed as long as the references are held: Destruction happens when the reference count of an object drops to zero - however, object `A' is owned by object `B', thus it's reference count will not be decreased until object `B' is destructed. But object `B' in turn is owned by object `A', and that reference is not released until object `A' is destructed. This circular dependency leads to neither objects being released automatically.

To prevent this scenario one reference can be made a csWeakRef<> instead. If object `B' holds only a weak reference to object `A', this reference will become null when `A' is destroyed, and `B' may also be destroyed if `A' held the last reference to it.

As all weak references to an object are automatically set to null when the object gets destroyed care must be taken when accessing a weak referenced object: the reference may become null at any time, after all. In practice that means that before dereferencing a weak reference you should check whether it still points to a valid object (with the IsValid() method or a comparison with 0):

 
class MyClass
{
  csWeakRef<iFoo> foo;
  ...
  void MyFunction ();
}

void MyClass::MyFunction ()
{
  // WRONG: Weak references may become 0 at any time!
  foo->BarMethod(...);
  // CORRECT: Check whether weak reference is valid before use
  if (foo.IsValid())
    foo->FooMethod(...);
  ...
}

What About csPtr<>?

csPtr<> is a companion class. Originally, it aided in the transition from the old (pre-smart pointer) API to the new one. The idea was that all functions that used to return a pointer, upon which the caller had to invoke DecRef(), now return a csPtr<>. These days, csPtr<> is usually used as a micro-optimization when transferring object ownership from a function to its caller, and as an ugly shortcut when assigning a new object to a csRef<> (instead of the more obvious csRef<>::AttachNew() method).

csPtr<> represents a single, owned, one-time-transferable reference to an object and should be used only as the return value of a function, or when creating a brand new object which is assigned directly to a csRef<>. csPtr<> never invokes IncRef() or DecRef(). It simply stores the pointer. csPtr<> is very specialized, and exists solely as a mechanism for transferring an existing reference into a csRef<>.

Although it is safest and cleanest for a function to transfer ownership of a new object back to its caller by returning a csRef<>, it is also possible to utilize csPtr<> for this purpose. This can be done as a micro-optimization in order to avoid the very minor overhead of the extra reference-count manipulation incurred when returning a csRef<> from a function. Note carefully, however, that you should never return a csPtr<> from a function if there is any chance that the caller might ignore the returned value since that would result in a resource leak. Returning a csRef<> ensures that the returned object can never be leaked, even if the caller neglects to assign it to a variable.

There is only one valid way to use the result of a function which returns a csPtr<>: assign it to a csRef<>. For example:

 
// An example interface and method.
struct iEngine
{
  virtual csPtr<iLight> CreateLight (...) = 0;
  ...
}

// Assignment of csPtr<> to csRef<>.
csRef<iLight> light (engine->CreateLight (...));
// or...
csRef<iLight> light = engine->CreateLight (...);
// or...
csRef<iLight> light;
...
light = engine->CreateLight (...);

When a csPtr<> is assigned to a csRef<>, the reference owned by the csPtr<> is transferred to the csRef<> without an additional IncRef(); that is, csRef<> inherits, steals, or hijacks the reference owned by the csPtr<>.

To make it easier for functions to actually return a csPtr<> even though they are working internally with a csRef<> there is also an explicit conversion from csRef<> to csPtr<>; which means that a csPtr<> can be constructed from a csRef<> if the csPtr<> constructor is called explicitly with a csRef<> as its sole argument. This means that the following code is valid:

 
csPtr<iFoo> MyFunction ()
{
  csRef<iFoo> foo = ...;
  ...
  return csPtr<iFoo> (foo);
}

What happens, in this case, is that the csPtr<> constructor which accepts a csRef<> will call IncRef() on the object. This is necessary because when the csRef<> inside MyFunction() goes out of scope it will call DecRef() automatically; potentially destroying the object.

The following usage, however, is incorrect:

 
iFoo* MyFunction ()
{
  csRef<iFoo> foo = ...;
  ...
  return foo;
}

This is incorrect because here nothing calls IncRef() on the returned pointer, yet the csRef<> will still call DecRef() upon destruction, which means, at best, the function is returning ownership of an object even though it does not hold ownership, and, at worst, it is potentially returning a destroyed object.

As noted above, the transfer of object ownership to the caller of a function should almost always be handled by returning a csRef<> or a csPtr<> rather than a bare iFoo*. However, if you really must return a normal pointer, then you have to ensure that you actually own a reference which you can return to the caller. Here is how the above example can be re-written so that it works correctly:

 
iFoo* MyFunction ()
{
  csRef<iFoo> foo = ...;
  ...
  foo->IncRef();
  return foo;
}

If you prefer obscurity and brevity over clarity, you can also use csPtr<> as a shortcut, in place of csRef<>::AttachNew(), when assigning a newly allocated object to a csRef<>. The following idiom ensures that the reference count of the new object is correctly maintained.

 
csRef<iView> view = csPtr<iView> (new csView (...));

This works correctly because the new object (`new csView') already automatically has a reference-count of 1 at construction time. By encapsulating the new object pointer in a csPtr<>, csRef<> is instructed to not invoke IncRef() on the incoming object, but rather to simply inherit the reference already owned by the csPtr<>. By contrast, the following code is incorrect and will result in a resource leak since the object's reference-count will be 2, rather than one; one reference from the `new' operation, and one from the IncRef() invocation performed by the csRef<>.

 
// Do not do this! It will cause a resource leak.
csRef<iView> view = new csView (...);

WARNING: Only use csPtr<> in the situations described above! Never use a csPtr<> to store an object. Never pass csPtr<> instances as arguments to other functions.

What to do With a Function Return Value?

In the Crystal Space API there are three possible modes by which reference-counted object pointers are returned:

What to do With a Function Parameter?

It's not uncommon that a reference to an object is to be taken as a function parameter. Using a smart pointer for that is usually not necessary: consider that you can usually assume that an object passed in as a parameter stays valid during the function execution (a scenario where this is not the case can even be considered pathological). This means that having the parameter keep a reference to the object passed in is unnecessary (and, considering that reference keeping incurs a small overhead, possibly even undesireable). As smart pointers automatically convert to normal pointers it's not a problem to declare parameters for references as normal pointers. Likewise, when the reference passed in is to be retained it's no problem to assign them to a csRef<> object to take ownership.

 
// Passing a csRef<iFoo> as parameter: usually unnecessary
void Bar (csRef<iFoo>& stuff) { ... }

// iFoo* is usually completely adequate
void Baz (iFoo* stuff) 
{ 
  ... 
  /* If 'stuff' runs the risk of being released you can always assign it to
   * a csRef<> in the function body. */
  csRef<iFoo> keepRef = stuff;
  ... 
}

Warnings About csPtr<>!

As noted above, only use csPtr<> for returning already incremented object references, and for wrapping a new object before storing it in a csRef<>. Do not use csPtr<> for any other purpose.

Also, when a function returns a csPtr you must assign the result to a csRef<>. You must not ignore the returned value. If you ignore it, then that will result in a resource leak because DecRef() will never be invoked for the reference owned by the returned csPtr<>. For example, the following code is illegal and should be avoided because it ignores the returned object:

 
// An example interface and method.
struct iEngine
{
  virtual csPtr<iLight> CreateLight (...) = 0;
  ...
}

// Do not do this!  It will cause a resource leak.
engine->CreateLight (...);

Note that if you build the project in debug mode, then Crystal Space will add a run-time test for this incorrect usage, and will throw an exception if you neglect to assign a returned csPtr<> to a csRef<>.

Warning About IncRef() and DecRef()!

When using smart pointers (csRef<>) correctly you should avoid invoking IncRef() and DecRef() on the managed pointer, except in very specialized cases, and only when you know exactly what you are doing and why you are doing it. Avoid constructs like this:

 
csRef<iMeshWrapper> mesh = ...;
...
mesh->DecRef ();
mesh = 0;

The bogus code in this example will cause the reference-count to be decremented twice--once when DecRef() is invoked explicitly, and once when 0 is assigned to `mesh')-which is almost certainly not what was intended.

Warning About DestroyApplication()!

Due to the way the current implementation of csInitializer::DestroyApplication() works, you must ensure that all of your references to Crystal Space objects are released before invoking DestroyApplication(). Therefore, the following code is not legal:

 
int main (int argc, char* argv[])
{
  iObjectRegistry* object_reg =
    csInitializer::CreateEnvironment(argc, argv);
  ...
  csRef<iPluginManager> plugin_mgr =
    (csQueryRegistry<iPluginManager> (object_reg));
  ...
  csInitializer::DestroyApplication (object_reg);
  return 0;
}

The reason this doesn't work correctly is that the `plugin_mgr' reference will be cleaned up at the end of main(), which occurs after DestroyApplication() is invoked. To fix this you can use several techniques. Manually setting `plugin_mgr' to 0 just before calling DestroyApplication() is one method. Another technique is to put the initialization into another routine so that `plugin_mgr' is created in another scope; a scope which is destroyed before DestroyApplication() is invoked.

For the same reason it also is not wise to call DestroyApplication() from within the destructor of your main class. This is because any csRef<> instance variables of your main class will not be destroyed until the very end of the destructor, which is after the invocation of DestroyApplication() in the body of the destructor.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated using texi2html 1.76.