cppsmartpointer

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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:

1
std::unique_ptr<foo> uptr6 = uptr5;

there is error message like this:

1
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.

shared_pointer

create shared pointer from raw pointer

this is that same with the deleter

1
2
3
4
5
6
7
8
9
10
11
12
13
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.

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

#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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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:

1
std::shared_ptr<foo> uptr2(f);

there is also memory problem such as:

1
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:

1
std::shared_ptr<foo> uptr2 = uptr1;

instead of creating a new shared pointer based on the same raw pointer.

references

http://www.cplusplus.com/reference/memory/unique_ptr/unique_ptr/

推荐文章