cpp smart pointer

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 {
public:
// default constructor
foo() { std::cout << "foo constructor is called" << std::endl; }
// copy constructor
foo(const foo& other) {
std::cout << "foo copy constructor is called" << std::endl;
}
// move constructor
foo(foo&& other) {
std::cout << "foo move constructor is called" << std::endl;
}

// destructor
~foo() { std::cout << "foo destructor is called" << std::endl; }

// assignment
foo& operator=(const foo& t) {
std::cout << "foo assignment operator is called" << std::endl;
return *this;
}

// move assignment
foo& operator=(foo&& other) {
std::cout << "foo move assignment operator is called" << std::endl;
return *this;
}
};

and we could test this class by unique ptr for several cases:

int main() {
std::cout << "start test1" << std::endl;
foo* f = new foo();
std::unique_ptr<foo> uptr1(f);

// std::cout << "start test2" << std::endl;
// std::unique_ptr<foo> uptr2(f);
// there is sig fault if the delete will be called twice for the unique ptr

std::cout << "start test3" << std::endl;
std::unique_ptr<foo> uptr3(new foo());

std::cout << "start test4" << std::endl;
foo* f4 = new foo();
// c++14 feature
std::unique_ptr<foo> uptr4 = std::make_unique<foo>(foo());

// this is the prefered way than use new direactly
std::cout << "start test5" << std::endl;
std::unique_ptr<foo> uptr5 = std::make_unique<foo>();

std::cout << "finish tests" << std::endl;
return 0;
}

//////output//////
start test1
foo constructor is called
start test3
foo constructor is called
start test4
foo constructor is called
foo constructor is called
foo move constructor is called
foo destructor is called
start test5
foo constructor is called
finish tests
foo destructor is called
foo destructor is called
foo destructor is called
foo destructor is called

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)
{
std::cout << "DELETER FUNCTION CALLED\n";
delete[] x;
}

int main (){

Sample * p3 = new Sample[3];
std::shared_ptr<Sample> g;
g.reset (p3,deleter);
...
}

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:


#include <iostream>
#include <memory>
#include <unistd.h>
struct Sample
{
Sample()
{
std::cout << "CONSTRUCTOR of Sample\n";
}
~Sample()
{
std::cout << "DESTRUCTOR of Sample\n";
}
};



// function that calls the delete [] on received pointer
void deleter(Sample * x)
{
std::cout << "DELETER FUNCTION CALLED\n";
delete[] x;
}

std::shared_ptr<Sample> g;

void testShare(){
// Creating a shared+ptr with custom deleter
std::shared_ptr<Sample> p3(new Sample[3], deleter);
g=p3;
return;
}

int main()
{
testShare();

std::cout<<"ok for executing test share" <<std::endl;
std::cout<<"do sth else" <<std::endl;
sleep(2);

return 0;
}

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() {
std::cout << "start test1" << std::endl;
foo* f = new foo();
std::shared_ptr<foo> uptr1(f);

std::cout << "start test2" << std::endl;
//this case is not ok
//std::shared_ptr<foo> uptr2(f);
//this case is ok
std::shared_ptr<foo> uptr2 = uptr1;
// there is sig fault if the delete will be called twice for the unique ptr

std::cout << "start test3" << std::endl;
std::shared_ptr<foo> uptr3(new foo());

std::cout << "start test4" << std::endl;
foo* f4 = new foo();
// c++14 feature
std::shared_ptr<foo> uptr4 = std::make_shared<foo>(foo());

// this is the prefered way than use new direactly
std::cout << "start test5" << std::endl;
std::shared_ptr<foo> uptr5 = std::make_shared<foo>();

std::cout << "start test6" << std::endl;
std::shared_ptr<foo> uptr6 = uptr5;

std::cout << "finish tests" << std::endl;
return 0;
}

//////output//////
start test1
foo constructor is called
start test2
start test3
foo constructor is called
start test4
foo constructor is called
foo constructor is called
foo move constructor is called
foo destructor is called
start test5
foo constructor is called
start test6
finish tests
foo destructor is called
foo destructor is called
foo destructor is called
foo destructor is called

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/

推荐文章