some tips and typical use examples about the cpp smart pointers
Use std::unique_ptr
for exclusive-ownership resource management.std::shared_ptr
for shared-ownership resource management.
unique_pointer
For example, we have a class like this:
class foo { |
and we could test this class by unique ptr for several cases:
int main() { |
let’s analyse the related test cases,
For the test1, we use new to allocate the memory and then use this pointer to init the unique pointer.
For test2, we let the two unique pointer to point to the same raw pointer, this will cause the memory problem, since the delete function is called twice.
For test3, there is not much differences compared with the test1
For test4, we use the make_unique
, if the parameters of the make unique is an class instance, the move constructor is called and the original class will be deleted. and the object is constructed twice according to the log, it shows that the unique ptr create an copy for the original object and delete the original one.
For the test5, which is also the prefered approach for init the unique pointer compared with the new operation, the default constructor is only called once and the created object is managed by the unique pointer. refer to the item21 for the effective modern cpp for more details.
If we try to copy the unique pointer by this:
std::unique_ptr<foo> uptr6 = uptr5; |
there is error message like this:
error: call to implicitly-deleted copy constructor of 'std::unique_ptr<foo>' |
since the unique pointer is exclusive and the copy constructor of this class is deleted, the compiler does not allow us to execute the copy operation for the unique ptr.
decide if the unique pointer is empty
The function returns true whenever the stored pointer is not a null pointer, thus returning the same as: get()!=nullptr
shared_pointer
create shared pointer from raw pointer
this is that same with the deleter
void deleter(Sample * x) |
create a shared pointer from object
we can use make_shared
to create a shared pointer from the scratch. This contains two steps, we create an object and then wrap it as a shared pointer. The return value is a shared pointer.
get raw pointer from shared pointer
just use ptr.get()
copy the contents pointed by raw data into the shared pointer space
There are two solutions,
first is to reset the shared pointer to the memory space where raw pointer pointed to, use the reset function of the shared pointer. refer to this. In this case, there is only one memory space.
second is that the shared pointer will point to the new allocated space, and it will get data from the raw space, the std::copy
function could be used in this case, copy data from the source to the destination.
std::copy(rpt,rpt+elemSize,sharedPtr.get()) |
when the destructor will be called
The destructor of the shared pointer will be called when there is no reference bind with the memory space.
for example:
|
destructor will be called after the testShare();
if there is no shared pointer in global domain, the object will be release after testShare()
function.
If we use the same object to test the shred ptr, there are following outputs:
int main() { |
we could see that it is ok to copy the shared pointer back and forth between each other, but we could not init two shared pointer based on one raw pointer. if we use the following operation for the test2:
std::shared_ptr<foo> uptr2(f); |
there is also memory problem such as:
pointer being freed was not allocated |
since the new shared pointer does not know that this pointer have managed by the the other raw pointer. The workable way is just copy the shared pointer between each other if it is needed by other classes or function such as this way:
std::shared_ptr<foo> uptr2 = uptr1; |
instead of creating a new shared pointer based on the same raw pointer.
more discussion
This is a good question that discuss all aspects about the shared pointer.
Instead of describing these details, just mention some key concepts.
The first is the RAII, see details here, it is a good habit to clean things finally after you initilize and use it.
Another concept is the ownership, if we do not need the ownership, the T*
is a good thing to do.
What pointers are not good at is representing ownership and directly support safe iteration
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1408r0.pdf).
That means when you need to represent the ownership, it is prefered to use the unique pointer or shared pointer, when you need to iterate things, it is good to use suitable stl to wrap your object.
But it looks that the weak_ptr
is a more suitable thing to provide the ability of access particular object but not own it, since we can know if the original object is destroyed or not, the raw pointer can not achieve this. Basically, if you care more about the safety than performance (or the simplicity of the interface), the shared ptr and weak ptr is good thing, other wise, just make sure what you point to by the raw pointer is sth alive manully.
just as the description here:
Notice that a call to this function does not make unique_ptr release ownership of the pointer (i.e., it is still responsible for deleting the managed data at some point). Therefore, the value returned by this function shall not be used to construct a new managed pointer. |
when you get the raw pointer from the unique pointer, just make sure that you only access it, but not own it. So you have the readable privilage, or write privilage some times, but you are not supposed to erase or construct other things based on it.
So where will we use the raw pointer in cpp world? I am still not sure, it looks that the raw pointer is a more low level abstraction in cpp world, and we can always find the replacement of the raw pointer such as reference in the function parameter and the smart pointer to own the object. The raw pointer is the foundation for these things, for example, the reference is implemented by const pointer.
When we have decided to use the smart pointer, just try to avoid delete things based on the raw pointer. Another thing is that, when we decide to use the shared pointer, just copy it back and forth instead of using the reference. Since the copy operation will increase the count, but if we use the reference, the count might not increase and the original object can be erased, such as the issue described here, the longest answer
references
http://www.cplusplus.com/reference/memory/unique_ptr/unique_ptr/