Работа с умными указателями (smartpointers)
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.В экземплярах UNIGINE классов C ++ API (таких как: Node, Mesh, Body, Image и т.д.) Хранятся только указатели на экземпляры внутренних классов C ++, они не могут быть созданы и удалены с помощью стандартных операторов new/delete. Поэтому они должны быть объявлены как умные указатели (Unigine::Ptr), которые позволяют автоматически управлять их временем жизни. UNIGINE имеет собственный оптимизированный распределитель памяти для более быстрого и эффективного управления памятью. Каждый умный указатель хранит счетчик ссылок, то есть количество умных указателей, указывающих на управляемый объект. Подсчет ссылок является потокобезопасным, поскольку изменение счетчика является атомарной операцией.
Not all methods of the 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.Не все методы внутренних классов C ++ движка доступны пользователю, некоторые из них используются только движком. Это особые функции, которые либо используются только для некоторых внутренних целей, либо не могут быть предоставлены пользователю «как есть». Итак, чтобы отфильтровать такие методы, используется промежуточный уровень, называемый интерфейсом . Этот интерфейс хранит указатель на экземпляр внутреннего класса C ++ Engine.
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.Чтобы создать экземпляр внутреннего класса, мы должны объявить для него умный указатель и вызвать метод create() - конструктор класса - при необходимости предоставив параметры создания.
// 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).Объекты Ownership (Image, Texture, Mesh, Tileset и т.д.) Эти объекты управляются в соответствии со счетчиком ссылок: когда последний умный указатель уничтожается, счетчик переходит на 0, а затем управляемый объект автоматически удаляется. В этом случае предполагается, что объект больше не нужен (движок ничего о нем не знает, и у пользователя нет указателя на его использование), и, следовательно, он удаляется (например, такие объекты, объявленные в области видимости, будут автоматически удалены при выходе из области действия).
// 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;
Complete list of Ownership Objects:Полный список объектов Ownership:
- Blob
- Camera
- Curve2d
- Dir
- Ellipsoid
- File
- GameIntersection
- Image
- ImageConverter
- Json
- LandscapeFetch
- LandscapeImages
- LandscapeMapFileCreator
- LandscapeMapFileSettings
- LandscapeTextures
- Mesh
- MeshDynamic
- MeshStatic
- ObjectIntersection
- ObjectIntersectionNormal
- ObjectIntersectionTexCoord
- PackageUng
- Path
- PathRouteIntersection
- PhysicsIntersection
- PhysicsIntersectionNormal
- RegExp
- RenderTarget
- Shader
- ShapeContact
- Socket
- Stream
- StructuredBuffer
- SystemDialog
- Texture
- TextureCurve
- TilesetFile
- UlonArg
- UlonNode
- UlonValue
- Viewport
- WorldIntersection
- WorldIntersectionNormal
- WorldIntersectionTexCoord
- Xml
-
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()). Non-Ownership объекты (узлы, виджеты, материалы, свойства и т.д.) - эти объекты взаимодействуют с движком и становятся управляемыми им с момента их создания (они участвуют в основном цикле, могут быть получены по именам и т.д.). Время жизни этих объектов не определяется счетчиком ссылок , они обеспечивают механизм слабых ссылок, поэтому вы можете проверить, был ли объект удален или нет. Для удаления таких объектов следует использовать deleteLater() или соответствующий метод менеджера (например: 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, 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.Управляемый миром (World) - в этом случае узел удаляется при закрытии мира. Эта политика используется по умолчанию для каждого нового узла.
- 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).Управляется движком (Engine) - в этом случае узел будет автоматически удален при выключении двигателя (может использоваться для узлов, которые должны сохраняться при смене миров).
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:Иногда (например, когда мы используем World::getNodeByName() и т.д.) Мы получаем значение NodePtr, которое является указателем на базовый класс, но для выполнения операций с определенным объектом (например, ObjectMeshDynamicPtr) нам нужно выполнить сужение типа или downcasting (т.е. перейти от указателя на базовый класс к указателю на производный). Для этого имеются следующие методы:
- static_ptr_cast — static casting without any checks (in accordance with C++ semantics)static_ptr_cast - статическое приведение без каких-либо проверок (в соответствии с семантикой C ++)
- checked_ptr_cast — downcasting with automatic type checking performedchecked_ptr_cast - сужение типа с автоматической проверкой типов
- dynamic_ptr_cast — dynamic casting (in accordance with C++ semantics)dynamic_ptr_cast - динамическое приведение (в соответствии с семантикой C ++)
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.Иногда вам также может потребоваться выполнить расширение типа или upcasting (от указателя на производный класс к указателю на базовый класс), этот тип преобразования выполняется автоматически.
The code samples below demonstrate the points described above.Примеры кода ниже демонстрируют пункты, описанные выше.
Example 1Пример 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 the 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.Умный указатель имеет деструктор clear(), предназначенный для объектов ownership, очищающих указатель и удаляющих объект только в том случае, если умный указатель, вызывающий этот метод, является последним, указывающим на объект (в данном случае интерфейс). Это следует учитывать.
As for non-ownership objects, they can be deleted via one of the following methods:Что касается объектов non-ownership, их можно удалить одним из следующих методов:
- 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;deleteLater() - выполняет отложенное удаление, в этом случае объект будет удален на следующем этапе swap() основного цикла (рендеринг объекта немедленно прекращается, но он все еще существует в памяти некоторое время, поэтому вы можете получить его от его родителя, Например). Этот метод упрощает удаление объекта из вторичного потока, поэтому вы можете вызвать его и забыть о деталях, позволяя движку взять на себя управление процессом удаления, которое можно использовать для будущих оптимизаций;
- 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.deleteForce() - выполняет немедленное удаление, которое может быть необходимо в некоторых случаях. Вызов этого метода для объектов, зависящих от основного цикла (например, узлов), безопасен только при выполнении из основного потока.
Both these methods can be safely called more than once for a single object (as well as after an object has been deleted by the Engine) without causing a double deletion. So, no worries, call it whenever an object is no longer needed.Оба эти метода можно безопасно вызывать более одного раза для одного объекта (а также после того, как объект был удален движком), не вызывая двойного удаления. Так что не беспокойтесь, вызывайте его, когда объект больше не нужен.