STL Smart Pointers

看书的时候跳过了这一节,被抓个正着。・゜・(ノД`)

C98提供的auto_ptr<>由于当时语言特性缺失(比如构造函数的move),有很多问题,一直被各种C教材不建议使用。
C++11后,智能指针部分也添加很多新功能,auto_ptr<>也被正式废弃了。

从C++11开始,STL提供2种智能指针:

  1. shared_ptr表示shared ownership。多个智能指针指向同一个资源,当最后一个指向该资源的指针被删除时,该资源被释放。如果遇到更复杂的情况,可以使用一些帮助类,如weak_ptrbad_weak_ptrenable_shared_from_this等。
  2. unique_ptr表示exclusive ownership或strict ownership。这个智能指针保证同时只有一个该指针指向某一资源。不过所有权是可以变更的。这个智能指针尤其适用于防止内存泄露的场合,比如忘了调用delete删除new出来的对象时。

所有的智能指针都定义在头文件<memory>里。

Class shared_ptr

shared ownership。最后一个走的记住锁门。

Using Class shared_ptr

可以assign/copy/compare,也可以用*->
例子:

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
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
int main()
{

// two shared pointers representing two persons by their name
shared_ptr<string> pNico(new string("nico"));
shared_ptr<string> pJutta(new string("jutta"));
// capitalize person names
(*pNico)[0] = 'N';
pJutta->replace(0,1,"J");
// put them multiple times in a container
vector<shared_ptr<string>> whoMadeCoffee;
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
// print all elements
for (auto ptr : whoMadeCoffee)
{
cout << *ptr << " ";
}
cout << endl;
// overwrite a name again
*pNico = "Nicolai";
// print all elements again
for (auto ptr : whoMadeCoffee)
{
cout << *ptr << " ";
}
cout << endl;
// print some internal data
cout << "use_count: " << whoMadeCoffee[0].use_count() << endl;
}
//运行结果:
/*
Jutta Jutta Nico Jutta Nico
Jutta Jutta Nicolai Jutta Nicolai
use_count: 4
*/

创建方式:

1
2
shared_ptr<string> pNico{new string("nico")};
shared_ptr<string> pNico = make_shared<string>("nico"); //better

可以先声明再赋值:

1
2
shared_ptr<string> pNico;
pNico.reset(new string("nico"));

Defining a Deleter

可以自定义deleter。

1
2
3
4
5
shared_ptr<string> pNico(new string("nico"),
[](string* p) {
cout << "delete " << *p << endl;
delete p;
});

Dealing with Arrays

shared_ptr删除时默认调用delete,而非delete[]。

1
std::shared_ptr<int> p(new int[10]);	// ERROR, but compiles

这种时候应该自定义函数/函数对象/lambda做为deleter。

1
2
3
4
std::shared_ptr<int> p(new int[10],
[](int* p) {
delete[] p;
});

Dealing with Other Destruction Policies

删除时的额外操作。
例如,当最后一个引用被移除时,希望删除一个临时文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <fstream>
#include <string>
#include <cstdio>
#include <memory>
class FileDeleter
{
private:
std::string filename;
public:
FileDeleter(const std::string& fn) : filename(fn){}
void operator()(std::ofstream* fp)
{

fp->close(); // close file
std::remove(filename.c_str()); // delete file
}
};
int main()
{

// create and open temporary file:
std::shared_ptr<std::ofstream> fp(new std::ofstream("tmpfile.txt"), FileDeleter("tmpfile.txt"));
}

不过更好的办法其实是创建一个新类,构造函数进行初始化工作,析构函数做清理。只用shared_ptr们管理这个类new出的对象。

Class weak_ptr

某些情况下,shared_ptr并不适用,例如:

  • 循环指向,两个智能指针分别指向对方。如果是shared_ptr会导致无法释放数据,因为他们的use_count()都还是1。
  • 想要显式分享但不持有一个对象。shared_ptr会导致无法释放该对象。普通指针则有可能变成野指针。

这些情况下就要用weak_ptr了,可以分享但并不持有一个对象。创建时需要指定一个shared_ptr,当最后一个shared_ptr被释放时,这个weak_ptr也就自动变成空了。
weak_ptr不提供*->,而是通过从其创建出一个shared_ptr来控制引用对象。因为这样才能保证操作时该对象会存在。
所以weak_ptr提供的操作很少:只能创建、复制、赋值为shared-ptr和检查指向对象是否存在。

Using Class weak_ptr

通过使用weak_ptr将双向指向变成单向。

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
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std:
class Person
{
public:
string name;
shared_ptr<Person> mother;
shared_ptr<Person> father;
vector<weak_ptr<Person>> kids; // weak pointer
Person(const string &n, shared_ptr<Person> m = nullptr, shared_ptr<Person> f = nullptr) : name(n), mother(m), father(f)
{}
~Person(){ cout << "delete " << name << endl;}
};
shared_ptr<Person> initFamily(const string &name)
{
shared_ptr<Person> mom(new Person(name+"'s mom"));
shared_ptr<Person> dad(new Person(name+"'s dad"));
shared_ptr<Person> kid(new Person(name, mom, dad));
mom->kids.push_back(kid);
dad->kids.push_back(kid);
return kid;
}
int main()
{

shared_ptr<Person> p = initFamily("nico");
cout << "nico's family exists" << endl;
cout << "- nico is shared " << p.use_count() << " times" << endl;
cout << "- name of 1st kid of nico's mom: " << p->mother->kids[0].lock()->name << endl; // notice lock
p = initFamily("jim");
cout << "jim's family exists" << endl;
}
// 输出
/*
nico's family exists
- nico shared 1 times
- name of 1st kid of nico's mom: nico
delete nico
delete nico's dad
delete nico's mom
jim's family exists
delete jim
delete jim's dad
delete jim's mom
*/

lock操作从weak_ptr中复制出一个shared_ptr。如果该weak_ptr所指向的对象已经被释放,复制出的shared_ptr也是空的,可能会引起未定义操作。
如果不确定weak_ptr所指向的对象是否还存在,有几种处理方法:

  1. 调用expired(),如果weak_ptr指向对象已不存在就会返回true。等价于用use_count()判断是否是0,并且更快一些。
  2. 可以从weak_ptr到shared_ptr显式转化,用shared_ptr的构造函数。如果对象不存在,会抛出一个bad_weak_ptr错误。
  3. 可以用use_count()查询对象引用数目,如果是0就算了。STL上明确说了use_count()可能性能不怎样,所以只能在debug时用。

例如:

1
2
3
4
5
6
7
8
9
10
11
try {
shared_ptr<string> sp(new string("hi")); // create shared pointer
weak_ptr<string> wp = sp; // create weak pointer out of it
sp.reset(); // release object of shared pointer
cout << wp.use_count() << endl; // prints: 0
cout << boolalpha << wp.expired() << endl; // prints: true
shared_ptr<string> p(wp); // throws std::bad_weak_ptr
}
catch (const std::exception &e) {
cerr << "exception: " << e.what() << endl; // prints: bad_weak_ptr
}

Misusing Shared Pointers

除了要小心循环指向,还要小心同时只能有一组shared_ptr持有某个对象。不然同一对象会被delete两次而出错。
错误写法:

1
2
3
int *p = new int;
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p); // ERROR: two shared pointers manage allocated int.

正确写法:

1
2
shared_ptr<int> sp1(new int);
shared_ptr<int> sp2(sp1); // OK

继承std::enable_shared_from_this<>允许用shared_from_this()得到本类的唯一智能指针,但不能在构造函数中调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person : public std::enable_shared_from_this<Person>
{
public:
...
void setParentsAndTheirKids(shared_ptr<Person> m = nullptr, shared_ptr<Person> f = nullptr)
{

mother = m;
father = f;
if (m != nullptr)
{
m->kids.push_back(shared_from_this()); //OK
}
if (f != nullptr)
{
f->kids.push_back(shared_from_this()); //OK
}
}
...
};

Shared and Weak Pointers in Detail

总结shared_ptr和weak_ptr。

Class shared_ptr in Detail

shared ownership.

定义:

1
2
3
4
5
6
7
8
9
namespace std {
template <typename T>
class shared_ptr
{
public:
typedef T element_type;
...
}
}

其中T可以是void,就类似void*。

shared_ptr提供的所有操作:
Operations of shared_ptr
其中get_deleter的用法。

1
2
3
4
5
auto del = [](int *p) {
delete p;
};
std::shared_ptr<int> p(new int, del);
decltype(del)* pd = std::get_deleter<decltype(del)>(p);

More Sophisticated shared_ptr Operations

使用aliasing constructor要注意,表示了一个对象拥有另一个对象的关系。程序员要注意保证他们生存期是一致的。

1
2
3
4
struct X
{int a;};
shared_ptr<X> px(new X);
shared_ptr<int> pi(px, &px->a); // X owns a. 创建指向a的指针需要保证其所在X存在,所以用aliasing constructor。

一个错误示例:TODO

1
2
3
4
shared_ptr<X> sp1(new X);
shared_ptr<X> sp2(sp1, new X); // ERROR: delete for this X will never be called
sp1.reset(); // deletes first X; makes sp1 empty
shared_ptr<X> sp3(sp1, newX); // use_count()==0, but get()!=nullptr

make_shared<X>(...)比较好。

使用cast:

1
2
shared_ptr<void> sp(new int);	// shared pointer holds a void* internally
static_pointer_cast<int*>(sp); // OK

Class weak_ptr in Detail

share an object without owing it.
定义:

1
2
3
4
5
6
7
8
9
namespace std {
template <typename T>
class weak_ptr
{
public:
typedef T element_type;
...
}
}

Operations of weak_ptrs

Thread-Safe Shared Pointer Interface

shared_ptr不是线程安全的。
有重载版的shared_ptr可以做到指针自己的线程安全。如:

1
2
3
4
5
6
7
std::shared_ptr<X> global;	// initially nullptr
void foo()
{

std::shared_ptr<X> local{new X};
...
std::atomic_store(&global,local);
}

High-Level Atomic Operations of shared_ptr

Class unique_ptr

当程序出现exception时仍能防止内存泄露。exclusive ownership。

Using a unique_ptr

和普通指针接口差不多。

1
2
3
4
5
// create and initialize (pointer to) string:
std::unique_ptr<std::string> up(new std::string("nico"));
(*up)[0] = 'N'; // replace first character
up->append("lai"); // append some characters
std::cout << *up << std::endl; // print whole string

但是没有指针算术如++之类的操作。可以是空的即不持有一个对象。

1
2
3
std::unique_ptr<std::string> up;
up = nullptr;
up.reset();

用release来把所有权交出。

1
2
std::unique_ptr<std::string> up(new std::string("nico"));
std::string* sp = up.release(); // up loses ownership

检查unique_ptr是否为空。

1
2
3
if (up) {}	// if up is not empty
if (up != nullptr) {} // if up is not empty
if (up.get() != nullptr){} // if up is not empty

Transfer of Ownership by unique_ptr

程序员需要保证从同一个普通指针初始化的unique_ptr只有一个。

1
2
3
std::string *sp = new std::string("hello");
std::unique_ptr<std::string> up1(sp);
std::unique_ptr<std::string> up2(sp); // RUNTIME ERROR: up1 and up2 own same data

如果要转移,应用C++11的move。如果被转移的unique_ptr之前指向别人,别人被删除。

1
2
3
4
5
6
7
8
// initialize a unique_ptr with a new object
std::unique_ptr<ClassA> up1(new ClassA);
// transfer ownership of the unique_ptr
std::unique_ptr<ClassA> up2(std::move(up1));
// delete old object and own new
up2 = std::unique_ptr<ClassA>(new ClassA);
// delete the associated object
up2 = nullptr;

Source and Sink

Sink指的是外部获得的资源通过unique_ptr转移进某函数,然后在某函数内随着函数执行完毕被消灭。

1
2
3
void sink(std::unique_ptr<ClassA> up){...}	// sink() gets ownership
std::unique_ptr<ClassA> up(new ClassA);
sink(std::move(up)); // up loses ownership

Source指的是某函数可以被用作数据源,每次传一个新的来,用完即弃。之所以返回值不用加move是因为C++11有规定,编译器会自动尝试move。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::unique_ptr<ClassA> source()
{
std::unique_ptr<ClassA> ptr(new ClassA); // ptr owns the new object
...
return ptr; // transfer ownership to calling function
}
void g()
{

std::unique_ptr<ClassA> p;
for (int i = 0; i < 10; ++i)
{
p = source(); // p gets ownership of the returned object (previously returned object of f() gets deleted)
}
}

unique_ptr as Members

在类中使用unique_ptr也可以防止资源泄露。几乎都用不上析构函数了。必须提供拷贝构造函数和赋值运算符重载,因为unique_ptr原生不支持。

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
class ClassB
{
private:
std::unique_ptr<ClassA> ptr1; // unique_ptr members
std::unique_ptr<ClassA> ptr2;
public:
// constructor that initializes the unique_ptrs
// - no resource leak possible
ClassB (in val1, int val2) : ptr1(new ClassA(val1), ptr2(new ClassA(val2)))
{}
// copy constructor
// - no resource leak possible
ClassB (const ClassB& x) : ptr1(new ClassA(*x.ptr1)), ptr2(new ClassA(*x.ptr2))
{}
// assignment operator
const ClassB& operator= (const ClassB& x)
{
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
// no destructor necessary
// (default destructor lets ptr1 and ptr2 delete their objects)
...
};

Dealing with Arrays

unique_ptr默认删除用delete而非delete[]。
应该用:

1
2
std::unique_ptr<std::string[]> up(new std::string[10]);
std::cout << up[0] << std::endl;

Deleters for Other Associated Resources

与定义shared_ptr的稍有不同,必须在模板的第二个参数指定deleter的类型,如函数对象或函数指针或函数。
函数对象:

1
2
3
4
5
6
7
8
9
10
11
// function object
class ClassADeleter
{
public:
void operator () (ClassA* p) {
std::cout << "call delete for ClassA object" << std::endl;
delete p;
}
};
...
std::unique_ptr<ClassA,ClassADeleter> up(new ClassA());

lambda:

1
2
3
4
5
6
// lambda 1
std::unique_ptr<int,void(*)(int*)> up(new int[10],
[](int* p) {
...
delete[] p;
});
1
2
3
4
5
6
// lambda 2
std::unique_ptr<int,std::function<void(int*)>> up(new int[10],
[](int* p) {
...
delete[] p;
});
1
2
3
4
5
auto l = [](int* p) {
...
delete[] p;
};
std::unique_ptr<int,decltype(l)>> up(new int[10], l);

为了躲避填写deleter的类型,可以用C++11提供的alias template。这样接口就和shared_ptr差不多了。

1
2
3
4
5
6
7
template <typename T>
using uniquePtr = std::unique_ptr<T,void(*)(T*)>; // alias template
...
uniquePtr<int> up(new int[10], [](int* p) { // used here
...
delete[] p;
});

Class unique_ptr in Detail

exclusive ownership。
定义:

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
namespace std {
// primary template:
template <typename T, typename D = default_delete<T>>
class unique_ptr
{
public:
typedef ... pointer; // may be D::pointer
typedef T element_type;
typedef D deleter_type;
...
T& operator*() const;
T* operator->() const noexcept;
...
};
// partial specialization for arrays:
template<typename T, typename D>
class unique_ptr<T[], D>
{
public:
typedef ... pointer; // may be D::pointer
typedef T element_type;
typedef D deleter_type;
...
T& operator[](size_t i) const;
...
}
}

Operations of unique_ptrs
其中变量是一个pointer和一个deleter的构造函数重载多个。TODO

1
2
3
4
5
D d; //instance of the deleter type
unique_ptr<int, D> p1(new int, D()); // D must be MoveConstructible
unique_ptr<int, D> p2(new int, d); // D must be CopyConstructible
unique_ptr<int, D&> p3(new int, d); // p3 holds a reference to d
unique_ptr<int, const D&> p4(new int, D()); //Error: rvalue deleter object, can’t have reference deleter type

为数组特化的类型与普通unique_ptr的不同之处是:

  • 提供[]而非*->
  • 默认deleter调用delete[]而非delete。
  • 不支持类型转换。所以派生类指针就不能这样用了。

Final Words on Smart Pointers

Performance Issues

shared_ptr和weak_ptr支持通用类型,不能做特化的优化,内部需要的帮助对象就比较多,所以性能比较差。
unique_ptr没有这些问题,如果deleter使用lambda则性能更优化。

Usage Issues

永远不要从一个原生指针创建出多个智能指针。
shared_ptr和unique_ptr处理数组的接口不太一样,要注意。
智能指针一般情况下并不是线程安全的。

注意:智能指针陷阱

  • 不使用相同的内置指针值初始化(或reset)多个智能指针。
  • 不delete get()返回的指针。
  • 不使用get()初始化或reset另一个智能指针。
  • 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
  • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

[1] The C++ Standard Library 2nd Edition
[2] C++ Primer, 5th Edition