The problems that occurs with dynamic allocation relates to the manual memory management as mentioned previously. Two important issues that can arise are dangling pointers and memory leaks. The former refers to an instance in which a pointer attempts to access memory that has previously been freed. The latter relates to forgetting to deallocate memory leaving it inaccessible and unusable during the execution of the program. If this is done repeatedly, the amount of memory available to other programs reduces, eventually causing instability and crashes of the system. Therefore, there is a significant risk involved in using dynamic memory, however it is sometimes unavoidable. With the release of C++ 11, came the introduction of smart pointers, a feature that makes it easier to handle dynamic memory.
A unique pointer (unique_ptr) is a smart pointer that has exclusive ownership semantics. What this means is that the resource can only be owned by the unique pointer, and when this pointer goes out of scope, the resource is released. The only way for a unique_ptr to “share” the resource is to transfer ownership of that resource using the move semantic.
A shared pointer (shared_ptr) uses the concept of shared ownership semantics. Multiple shared pointers can refer to a single object, when another shared pointer refers to a resource, a reference count is increased. Only when the last shared pointer goes out of scope, is the memory released.
A weak pointer (weak_ptr) acts like a shared pointer in that it provides sharing semantics, however a weak pointer does not own the resource. Meaning that someone else, a shared pointer, must already own a resource that weak pointer can share. Furthermore, the number of weak pointers do not contribute to the reference count of a resource, therefore a resource will be released even if weak pointers still share that resource. This means that a weak pointer cannot access the object it shares. In order to do so a shared pointer must be created from this weak pointer.The reason this is done is so that the resource cannot be destroyed whilst being used by the weak pointer because there will be at least one shared pointer accessing the resource. This does not prevent a weak pointer from accessing a resource that no longer exists.
A unique pointer is meant solely for single ownership. For example, if you were to have an engine class that had the responsibility of creating and initialising the subsystems of an engine, like the renderer, or physics system. Then it is not unreasonable to assume that the Engine class would be the sole owner of each subsystem and would store each in a unique pointer. When the Engine class is destroyed which would typically be when the game using it is closed, then all the subsystems would be shut down, and the resources related to these systems freed when the instance of Engine goes out of scope. Although ownership remains with Engine, the subsystems can still be accessed by other parts of the game engine by referring to the pointer stored by the unique pointer, accessed through the get() function. This allows other objects access to the resource, the desired system.
A weak pointer doesn’t have ownership but has the ability to share a resource and can be constructed to share the resources of a shared pointer, not a unique pointer. Therefore, a weak pointer can be useful in situations involving shared pointers where you don’t want a function or class accessing the resources of the shared pointer to also control ownership of the resource. Going back to the idea of a game engine again. If we were to have a game object class that stored a list of all the components attached to it, suppose we wanted the components to communicate with other components in the same game object. One idea would be to allow the component to hold a reference to the its parent, the game object. This reference could be stored as weak pointer because it doesn’t make sense for the component to be able to destroy the game object, its parent The game object would most likely have ownership over the components, and these components would be destroyed before the game object.
A shared pointer can be used in situations in which a unique or weak pointer are implemented except for when using them causes a cyclic dependency. This relates back to the second example with game objects and components. If we have a shared pointer in game object that stores a component, and a component stored in the game object holds a shared pointer to the game object, its parent as shown below.