[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Written by Jorrit Tyberghein, jorrit.tyberghein@uz.kuleuven.ac.be. 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'.
This is easy. For example, often you want to keep track of a few common objects in your main class (like 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 don't 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 = CS_QUERY_REGISTRY (object_reg, iEngine); engine->CreateSector (); |
That's 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 = CS_QUERY_REGISTRY (object_reg, iEngine); 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 (SCF_QUERY_INTERFACE ( sprite->GetMeshObject (), iSprite3DState)); state->SetAction ("default"); |
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 () { iFoo* foo = new Foo(...); foo->SetupFoo(); ... return csPtr<iFoo> (foo); } |
The reason that this is 100% safe is that the newly created object is correctly destroyed (not leaked) even if the caller forgets or neglects to assign it to a variable.
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 a shortcut when assigning a new
object to a csRef<>
.
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 an
object 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<>
, and that is to 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 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<>
. This means that the
following code is valid:
csPtr<iFoo> MyFunction () { csRef<iFoo> foo = ...; ... return csPtr<iFoo> (foo); } |
What happens above 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 is, however, incorrect:
iFoo* MyFunction () { csRef<iFoo> foo = ...; ... return foo; } |
This is incorrect because nothing here 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; } |
Another way to use csPtr<>
is as follows:
csRef<iView> view = csPtr<iView> (new csView (...)); |
This is a very useful idiom for creating a new object and assigning it to a
csRef<>
while maintaining the proper reference count. 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.
In the Crystal Space API there are three possible modes by which reference-counted object pointers are returned:
csRef<>
. If you assign the return value to a normal pointer, then you
will not own a reference to the returned object. If you assign it to a
csRef<>
, then the csRef<>
will increment the reference count at
assignment time, and decrement it again when the csRef<>
destroyed.
csRef<>
. In this case the function is requesting
that you take ownership of the returned object because it (the function) will
be giving up ownership. By taking ownership, you ensure that the object does
not get destroyed prematurely. You can assign the returned csRef<>
to
another csRef<>
. It is also safe to not store the pointer but to
instead directly use it like this: Function()->DoSomething()
. It is
not safe to assign the result of a function returning a csRef<>
to a normal pointer because that will result in a resource leak since, after
assignment, an immediate DecRef()
will occur which may cause destruction
of the object. Finally, it is safe to ignore the return value from a function
returning a csRef<>
since the csRef<>
will ensure that the owned
reference is properly cleaned up even if you neglect to assign it to a variable
or use it directly.
csPtr<>
. In this case the function is demanding
that you take ownership of the returned object because it (the function) will
be giving up ownership. You must assign the returned csPtr<>
to a
csRef<>
in order to utilize it. The csRef<>
will inherit the
reference from the csPtr<>
. When the csRef<>
, to which the
csPtr<>
was assigned, is finally destroyed, the reference count will be
decremented properly. It is never safe to ignore the return value from
a function returning a csPtr<>
since doing so would result in a resource
leak.
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
runtime test for this incorrect usage, and will throw an exception if you
neglect to assign a returned csPtr<>
to a csRef<>
.
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, which is almost certainly not what was intended.
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 = (CS_QUERY_REGISTRY (object_reg, iPluginManager)); ... csInitializer::DestroyApplication (object_reg); return 0; } |
The reason this doesn't work 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 `NULL' just before
calling DestroyApplication()
is one method. Another way 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 ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |