Working with Smart Pointers
Some Basics#
In UNIGINE instances of C++ API classes (such as: Node, Mesh, Body, Image and so on...) only store pointers to instances of internal C++ classes, they cannot be created and deleted via the standard new / delete operators. So they should be declared as smart pointers (Unigine::Ptr) that allow you to automatically manage their lifetime. UNIGINE has its own optimized memory allocator for faster and more efficient memory management. Each smart pointer stores a reference counter, i.e. how many smart pointers are pointing to the managed object. Reference counting is thread-safe, as modifying the counter is an atomic operation.
Not all methods of Engine's internal C++ classes are exposed to the user, some of them are used by the Engine only. These are specific functions that either are used only for some internal purposes, or cannot be given to the user "as is". So, to filter out such methods an intermediate level, called interface, is used. This interface stores a pointer to the instance of the Engine's internal C++ class.
To create an instance of an internal class we should declare a smart pointer for it and call the create() method - class constructor - providing construction parameters if necessary.
// instantiating an object of an internal class
<Class>Ptr instance = <Class>::create(<construction_parameters>);
Lifetime#
We can divide all objects into two groups based on the way their lifetime is managed:
- ownership objects (Image, Texture, Mesh, Tileset, etc.) these objects are managed in accordance with reference counter: when the last smart pointer is destroyed, the counter goes to 0, and the managed object is then automatically deleted.
In this case it is assumed that the object is no longer needed (the Engine doesn’t know anything about it, and the user has got no pointer to be able to use it) and, therefore, it is deleted. (e.g. such objects declared within a scope will be automatically deleted when leaving the scope).
// creating a new image ImagePtr img = Image::create(); // now two pointers point to our image (reference counter increment) ImagePtr img2 = img; // removing the image (as both pointers no longer point to it and reference counter is zero) img2 = img = nullptr;
- non-ownership objects (nodes, widgets, materials, properties, etc.) - these objects interact with the Engine and become managed by it since the moment of their creation (they are engaged in the main loop, can be retrieved by names, etc.). The lifetime of these objects is not determined by the reference counter, they provide the mechanism of weak references, so you can check whether an object was deleted or not. To delete such objects you should use deleteLater() or a corresponding manager’s method (e.g.: Materials::removeMaterial()).
NodePtr node; void somefunc1(){ // creating a new dummy node node = NodeDummy::create(); } void somefunc2(){ // checking whether the node exists if (node) Log::message("The node is alive\n"); // deleting the node node.deleteLater(); }
Instead of managing references for nodes manually now you can simply choose lifetime management policy for it:
- World-managed - in this case a node shall be deleted when the world is closed. This policy is used by default for each new node.
- Engine-managed - in this case the node shall be deleted automatically on Engine shutdown (can be used for nodes that should be kept when changing worlds).
Upcasting and Downcasting#
Sometimes (e.g. when we use World::getNodeByName(), etc. ) we get a NodePtr value, which is a pointer to the base class, but in order to perform operations with certain object (e.g. ObjectMeshDynamicPtr) we need to perform downcasting (i.e. convert from a pointer-to-base to a pointer-to-derived).the following methods were introduced:
- static_ptr_cast - static casting without any checks (in accordance with C++ semantics)
- checked_ptr_cast - downcasting with automatic type checking performed
- dynamic_ptr_cast - dynamic casting (in accordance with C++ semantics)
Sometimes you may also need to perform upcasting (i.e. convert from a pointer-to-derived to a pointer-to-base) this type of casting is performed automatically.
The code samples below demonstrate the points described above.
Example 1
#include <UnigineEditor.h>
using namespace Unigine;
/* .. */
// find a pointer to node by a given name
NodePtr baseptr = World::getNodeByName("my_meshdynamic");
// cast a pointer-to-derived from pointer-to-base with automatic type checking
ObjectMeshDynamicPtr derivedptr = checked_ptr_cast<ObjectMeshDynamic>(baseptr);
// static cast: pointer-to-derived (File) from pointer-to-base (Stream)
if(stream->getType() == Stream::FILE)
FilePtr file = static_ptr_cast<File>(stream);
// upcast to the pointer to the Object class which is a base class for ObjectMeshDynamic
ObjectPtr object = derivedptr;
// upcast to the pointer to the Node class which is a base class for all scene objects
NodePtr node = derivedptr;
Deleting Objects#
A smart pointer has a clear() destructor intended for ownership objects clearing the pointer and deleting the object only in case if the smart pointer calling this method is the last one pointing to the object (interface, in this case). This should be taken into account.
As for non-ownership objects, they can be deleted via one of the following methods:
- deleteLater() - performs delayed deletion, in this case the object will be deleted during the next swap stage of the main loop (rendering of the object ceases immediately, but it still exists in memory for a while, so you can get it from its parent, for example). This method simplifies object deletion from a secondary thread, so you can call it and forget about the details, letting the Engine take control over the process of deletion, which can be used for future optimizations;
- deleteForce() - performs immediate deletion, which might be necessary in some cases. Calling this method for main-loop-dependent objects (e.g., nodes) is safe only when performed from the Main thread.
See Also#
- For more information on the methods of the Ptr class, see Ptr class page.