Tutorial:
demo_sharedptr.cpp
Pointers are a common source of programming mistakes. In fact, if you forget
to delete some object which has been previously created with new(), your program
will waste memory (memory leakage) - on the other hand, if you delete too early
an object while other parts of your program still point to it and want to use it
(dangling pointers) this causes program fault, because of memory access to RAM
which is already freed. In small projects, it is not too difficult to know when
to call delete() properly, but in large projects, especially if some objects are
shared by other objects (i.e. referenced by many pointers simultaneusly) it
could be difficult to avoid premature or too late call to delete(). Well, Chrono::Engine
makes this management of shared objects a lot easier, thank to smart pointers,
that is 'handles' which automatically perform the deletion of the shared
object when no one isn't referencing it anymore. You don't ever need to use
delete() when using spart pointers.
Note: no GUI: only text output in this demo.
#include "core/CHshared.h" #include "core/CHframe.h" using namespace chrono;
Implement a simple class, just to make some tests. This simple class is
inherited from the ChShared class.
Since it is inherited from the ChShared class, we will be able to use 'intrusive'
smart pointers, that is a very fast type of smart pointers based on reference
counting, the ChSharedPtr.
Note that you can still use smart pointers with other classes (not inherited
from ChShared), but using the non-intrusive smart pointers of type 'ChSmartPtr'
(a bit slower than ChSharedPtr..)
class cTest : public ChShared { public: int mfoo; cTest () { GetLog() <<" Building object of class cTest \n"; } ~cTest () { GetLog() <<" Deleting object of class cTest \n"; } };
Do the tests in the main() function.
int main(int argc, char* argv[]) {
EXAMPLE 1:
In C++ you create and delete dynamic data using the new ... delete
pair.
This is good in most cases, as in the simple example below..
{ GetLog() << " Example: use the classic new - delete method: \n"; cTest* mtest1 = new cTest; delete mtest1; }
EXAMPLE 2:
Well, if your oject is inherited from ChShared class, you can also use the
RemoveRef() function, which automatically deletes the object, instead of calling
'delete':
{ GetLog() << "\n Example: use AddRef() and RemoveRef() of ChShared class \n"; cTest* mtest2 = new cTest; // creation of ChShared objects sets to 1 the ref.count mtest2->RemoveRef(); // drops ref.count to 0, and automatically deletes }
EXAMPLE 3:
What's the use of this RemoveRef() stuff? Simple: if multiple pointers are
referencing the same object, you can use the AddRef()
function each time, then call RemoveRef() when you aren't interested anymore,
and deletion will happen automatically.
In detail, AddRef() increments a counter (initialized to 1 at obj.creation) and
each RemoveRef() decrements it, until reaches 0 and causes auto deletion.
{ GetLog() << "\n Example: use multiple AddRef() and RemoveRef() \n"; cTest* mtest2b = new cTest; // ref count =1 cTest* mtest3b = mtest2b; mtest3b->AddRef(); // ref count =2 mtest2b->RemoveRef(); // ref count =1 mtest3b->RemoveRef(); // ref count =0 ->automatic delete! }
EXAMPLE 4:
Ok, if you look at the previous example, there's still the risk of forgetting a
RemoveRef() or doing an extra RemoveRef() - in the first case you get memory
leaks, in the second, double deletion. Bad...
That's why here come the 'smart pointers', which take care AUTOMATICALLY
of memory management!
In this example, we use the ChSharedPtr smart pointers, which can be used for
classes inherited from ChShared. It uses intrusive policy, for maximum speed.
When smart pointers are deleted (as in this example, the compiler knows to get
rid of them automatically, when the {..} block ends ) they also take care of
deleting the object they share, if no one remains interested in it.
You can create a shared pointer using this syntax: ChSharedPtr<my_class>
pointer_name;
{ GetLog() << "\n Example: use smart pointers \n"; // Create an object which will be referenced (shared) by three // pointers. During creation set the first smart pointer to it... ChSharedPtr<cTest> mtest3(new cTest); // other pointer to the object can be created by () copy construction or.. ChSharedPtr<cTest> mtest4(mtest3); // other pointer to the object can be assigned by = operator ChSharedPtr<cTest> mtest5 = mtest3; // smart pointers behave exactly as traditional pointers... -> and . operators mtest4->mfoo = 120; (*mtest4).mfoo = 120; // convert to raw pointer with get_ptr(), if needed cTest* raw_pt = mtest4.get_ptr(); raw_pt->mfoo = 120; // HERE'S THE NICE PART!!! // Finally, you DO NOT NEED TO CALL delete(), because the // deletion will happen _automatically_ when the mtest3 and mtest4 // smart pointers will die (when exiting from the scope of this {..} // context). // This automatic deletion of shared objects is the MAIN REASON // of using the smart pointers. Memory handling is much easier // no memory leaks etc.). }
EXAMPLE 5:
Should you use smart pointers only with objects inherited from the
ChShared class? No..
In fact, you can also use non-intrusive smart pointers, so that your referenced
object does NOT need to be inherited
from ChShared class (but remember this is a bit slower..)
Note that the second template parameter of the ChSmartPtr is one of these
policies:
ChSharedPtrLinked (fast, use linked lists - suggested)
ChSharedPtrCounted (slower, use counters on heap)
ChSharedPtrIntrusive (behaves exactly as a ChSharedPtr)
{ GetLog() << "\n Example: use non-intrusive smart pointers \n"; ChSmartPtr<Quaternion, ChSharedPtrLinked> mtest3(new Quaternion); // other pointer to the object can be created by () copy construction or.. ChSmartPtr<Quaternion, ChSharedPtrLinked> mtest4(mtest3); // other pointer to the object can be assigned by = operator ChSmartPtr<Quaternion, ChSharedPtrLinked> mtest5 = mtest3; // smart pointers behave exactly as traditional pointers... -> and . operators mtest4->Normalize(); // Finally, you DO NOT NEED TO CALL delete(), because the // deletion of the instanced Quaternion object will happen _automatically_ }
That's all!,
return 0; }
NOTE: some common errors with smart pointers:
-1-
it's ok to assign a raw pointer to a smart pointer..
cTest* mrawptr = new cTest;
ChSharedPtr<cTest> mtestA(mrawptr);
..but if you assign AGAIN the same 'raw pointer' to ANOTHER smart ptr
you broke the automatic deletion mechanism (memory leak/corruption):
ChSharedPtr<cTest> mtestB(mrawptr);
BAD!!!!!!!!
..so you should do rather:
ChSharedPtr<cTest> mtestB(mtestA);
OK!!!
-2-
Another error is to create circular dependancies between objects with shared
pointers. This would cause memory
leakages.
There's a workaround. For example it's ok to point from object A to object B
with a shared pointer, than from B to A with a normal 'raw' pointer. It would be
a mistake to point 'back' from B to A with a second shared pointer becuase it
would mean circular dependancy.