智能指针用于解决常规指针所带来的内存泄露、重复释放、野指针等内存问题。智能指针基于这样的事实得以发挥作用:定义在栈中的智能指针,当超出其作用域时,会自动调用它的析构函数,从而可以释放其关联的内存资源。
之前C++标准库中定义的智能指针std::auto_ptr<T>,因其设计存在缺陷,所以已不再推荐使用。C++11引入了新的智能指针:unique_ptr、shared_ptr和weak_ptr。
一:unique_ptr
unique_ptr类似于auto_ptr。两个unique_ptr实例不能管理同一个对象。
1:类似于auto_ptr,unique_ptr也具有get成员函数,该函数返回指向被管理对象的指针;如果unique_ptr不拥有任何对象,则get返回nullptr;
2:它没有复制构造函数和赋值操作符,而具有移动构造函数和移动赋值操作符;当把源unique_ptr对象移动复制或移动赋值给目标unique_ptr对象后,源unique_ptr对象所掌握的资源的所属权,就转移给目的unique_ptr对象,而源unique_ptr对象就不再掌握资源,所以源unique_ptr对象的unique_ptr::get返回nullptr:
class X { int x; public: X(int x) : x(x) { cout << "ctor invoked: " << x << endl; } ~X() { cout << "dtor invoked: " << x << endl; } void sayHi() const { cout << "HI: " << x << endl; } }; int main() { unique_ptr<X> a(new X(2)); //unique_ptr<X> b(a); //unique_ptr<X> c; c = a; unique_ptr<X> d = std::move(a); cout << a.get() << endl; d->sayHi(); cout << "---------------\n"; unique_ptr<X> e(new X(3)); unique_ptr<X> f; f = std::move(e); cout << e.get() << endl; f->sayHi(); cout << "---------------\n"; }
unique_ptr没有复制构造函数和复制赋值操作符,只有移动构造函数和移动赋值操作符。所以,在复制或者赋值时,需要使用std::move将对象转换为右值。如果去掉了main函数中的注释代码,则会报编译错误: error: use of deleted function…
上面代码的运行结果如下:
ctor invoked: 2 0 HI: 2 --------------- ctor invoked: 3 0 HI: 3 --------------- dtor invoked: 3 dtor invoked: 2
当超出unique_ptr的作用域时,会自动删除它指向的内存对象;
3:unique_ptr的默认行为是掌管指向使用new申请的内存的指针的所属权,并在必要情况下使用delete释放内存。unique_ptr的声明如下:
template <typename T, typename Deleter = std::default_delete<T>> class unique_ptr;
模板类std::default_delete<T>实现了一个函数对象,调用时执行delete操作,类似于下方的实现:
template <typename T> class default_delete { public: void operator()(T* obj) const { delete obj; } };
可以用自己实现的释放资源函数对象,替换默认的deleter。比如,对于使用malloc申请的资源,如果需要用unique_ptr接管的话,必须使用free释放。例子如下:
struct my_point { int x; int y; }; struct my_point_deleter { void operator()(my_point* p) const { cout << "free " << p << endl; free(p); } }; void show_point(const my_point& o) { cout << "(" << o.x << ", " << o.y << ")" << endl; } int main() { unique_ptr<my_point, my_point_deleter> p(static_cast<my_point*>(malloc(sizeof(my_point)))); cout << "p is " << p.get() << endl; p->x = 5; p->y = 8; show_point(*p); }
运行结果如下:
p is 0x2443010 (5, 8) free 0x2443010
4:reset成员函数,用于更改unique_ptr所管理的对象,而原来所掌管的对象则被销毁。移动赋值操作A=B,A原来所掌管的对象也会被销毁:
int main() { unique_ptr<X> a(new X(2)); unique_ptr<X> b(new X(3)); cout << "---------------\n"; a = move(b); cout << "---------------\n"; a.reset(new X(4)); cout << "---------------\n"; a.reset(nullptr); cout << "---------------\n"; }
结果如下:
ctor invoked: 2 ctor invoked: 3 --------------- dtor invoked: 2 --------------- ctor invoked: 4 dtor invoked: 3 --------------- dtor invoked: 4 ---------------
5:只有非const的unique_ptr能够将其对某对象的所有权转移给其他unique_ptr。也就是说,如果a是个const unique_ptr对象,则下面的语句都是非法的:
a.reset(); b = std::move(a); unique_ptr<X> c(std::move(a));
6:unique_ptr实现了”*” 和 “->”操作符的重载。因此,可以像使用传统指针那样,使用unique_ptr;
7:unique_ptr实现了operator bool函数。也即是说,可以直接在if()中判断,该unique_ptr是否拥有某个对象:
int main() { std::unique_ptr<int> ptr(new int(42)); if (ptr) std::cout << "before reset, ptr is: " << *ptr << ‘\n‘; ptr.reset(); if (ptr) std::cout << "after reset, ptr is: " << *ptr << ‘\n‘; else std::cout << "after reset, ptr is null\n"; }
结果如下:
before reset, ptr is: 42 after reset, ptr is null
8:C++14引入了make_unique,用于更加方便的在堆上创建对象,并将该对象交给unique_ptr对象掌管。所以,下面两种写法是等价的:
unique_ptr<my_point> point(new my_point { 6, 5 }); auto point = make_unique<my_point>(6, 5);
使用make_unique隐藏了new的调用(that is good because we do not want to have our programs with new but without delete )
9:可以将unique_ptr放到vector中。因为unique_ptr不支持复制,所以在调用push_back时,unique_ptr对象必须是个右值:
int main() { std::vector< std::unique_ptr<X> > vu; vu.push_back(std::unique_ptr<X>(new X(2))); std::unique_ptr<X> a(new X(3)); vu.push_back(std::move(a)); for(auto iter = vu.begin(); iter != vu.end(); iter++) { (*iter)->sayHi(); } }
结果如下:
ctor invoked: 2 ctor invoked: 3 HI: 2 HI: 3 dtor invoked: 2 dtor invoked: 3
10:unique_ptr也支持管理动态申请的数组:
class X { int x; public: X(int x = 1) : x(x) { cout << "ctor invoked: " << x << endl; } ~X() { cout << "dtor invoked: " << x << endl; } void sayHi() const { cout << "HI: " << x << endl; } }; int main() { const int size = 3; std::unique_ptr<X[]> xs(new X[size]); for(int i = 0; i < size; i++) { xs[i].sayHi(); } }
结果如下:
ctor invoked: 1 ctor invoked: 1 ctor invoked: 1 HI: 1 HI: 1 HI: 1 dtor invoked: 1 dtor invoked: 1 dtor invoked: 1
二:shared_ptr
shared_ptr使用引用计数,允许多个shared_ptr管理堆中的同一个对象。只有引用计数变为0时,堆中的对象才被销毁。
多个shared_ptr共享同一对象的所有权,只能是通过复制构造函数或者复制赋值操作符实现。构造shared_ptr时,如果使用的底层指针已经被其他shared_ptr所有,这是一种未定义行为。
1:shared_ptr具有复制构造函数和移动构造函数,可以使用use_count成员函数返回有多少个shared_ptr拥有同一个对象。对于移动构造函数:A(std::move(B)),之后B不再拥有任何对象。
int main() { std::shared_ptr<X> sx1(new X(2)); std::cout << "sx1.get is " << sx1.get() << std::endl; std::cout << "sx1.use_count is " << sx1.use_count() << std::endl; std::cout << "---------------------\n"; std::shared_ptr<X> sx2(sx1); std::cout << "sx2.get is " << sx2.get() << std::endl; std::cout << "sx2.use_count is " << sx2.use_count() << std::endl; std::cout << "---------------------\n"; std::shared_ptr<X> sx3(std::move(sx1)); std::cout << "sx1.get is " << sx1.get() << std::endl; std::cout << "sx1.use_count is " << sx1.use_count() << std::endl; std::cout << "sx3.get is " << sx3.get() << std::endl; std::cout << "sx3.use_count is " << sx3.use_count() << std::endl; std::cout << "---------------------\n"; }
运行结果如下:
ctor invoked: 2 sx1.get is 0x12f5010 sx1.use_count is 1 --------------------- sx2.get is 0x12f5010 sx2.use_count is 2 --------------------- sx1.get is 0 sx1.use_count is 0 sx3.get is 0x12f5010 sx3.use_count is 2 --------------------- dtor invoked: 2
2:类似于unique_ptr,shared_ptr也支持自定义deleter。
struct my_point_deleter { void operator()(my_point* p) const { cout << "free " << p << endl; free(p); } }; int main() { std::shared_ptr<my_point> pm(static_cast<my_point*>(malloc(sizeof(my_point))), my_point_deleter()); std::cout << "pm.get is " << pm.get() << std::endl; pm->x = 1; pm->y = 2; show_point(*pm); std::cout << "-----------------\n"; }
这里构造shared_ptr对象pm时,第二个参数是my_point_deleter类对象,当需要释放shared_ptr所拥有的对象时,就会自动调用该对象的operator()函数。
结果如下:
pm.get is 0xd30010 (1, 2) ----------------- free 0xd30010
3:reset成员函数,用于更改shared_ptr所管理的对象,而原来所掌管的对象,其引用计数减1:
如果reset参数为空,则表示shared_ptr释放对源对象的拥有权;
注意,ptr不能是已经被其他shared_ptr所管理的对象,否则是未定义的;
int main() { std::shared_ptr<X> pm1; std::cout << "pm1.get is " << pm1.get() << std::endl; std::cout << "pm1.use_count is " << pm1.use_count() << std::endl; std::cout << "-----------------\n"; pm1.reset(new X); std::cout << "pm1.get is " << pm1.get() << std::endl; std::cout << "pm1.use_count is " << pm1.use_count() << std::endl; pm1->sayHi(); std::cout << "-----------------\n"; pm1.reset(new X(2)); std::cout << "pm1.get is " << pm1.get() << std::endl; std::cout << "pm1.use_count is " << pm1.use_count() << std::endl; pm1->sayHi(); std::cout << "-----------------\n"; pm1.reset(); std::cout << "pm1.get is " << pm1.get() << std::endl; std::cout << "pm1.use_count is " << pm1.use_count() << std::endl; std::cout << "-----------------\n"; return 0; }
结果如下:
pm1.get is 0 pm1.use_count is 0 ----------------- ctor invoked: 1 pm1.get is 0xebb010 pm1.use_count is 1 HI: 1 ----------------- ctor invoked: 2 dtor invoked: 1 pm1.get is 0xebb050 pm1.use_count is 1 HI: 2 ----------------- dtor invoked: 2 pm1.get is 0 pm1.use_count is 0 -----------------
4:复制赋值操作A=B,A原来所掌管的对象的引用计数减1,而B所掌管的对象的引用计数加1,而移动赋值操作符A=B,赋值之后,B不再掌握任何对象:
int main() { std::shared_ptr<X> pm1(new X(2)); std::shared_ptr<X> pm2(new X(3)); pm2 = pm1; std::cout << "pm1.get is " << pm1.get() << std::endl; std::cout << "pm2.get is " << pm2.get() << std::endl; std::cout << "pm1.use_count is " << pm1.use_count() << std::endl; pm2->sayHi(); std::cout << "-----------------\n"; std::shared_ptr<X> pm3; pm3 = std::move(pm1); std::cout << "pm1.get is " << pm1.get() << std::endl; std::cout << "pm2.get is " << pm2.get() << std::endl; std::cout << "pm3.get is " << pm3.get() << std::endl; std::cout << "pm2.use_count is " << pm2.use_count() << std::endl; pm3->sayHi(); std::cout << "-----------------\n"; }
结果如下:
ctor invoked: 2 ctor invoked: 3 dtor invoked: 3 pm1.get is 0x968010 pm2.get is 0x968010 pm1.use_count is 2 HI: 2 ----------------- pm1.get is 0 pm2.get is 0x968010 pm3.get is 0x968010 pm2.use_count is 2 HI: 2 ----------------- dtor invoked: 2
5:可以将shared_ptr放到容器中。
int main() { std::shared_ptr<X> sx1(new X(2)); std::vector< std::shared_ptr<X> > vs; vs.push_back(sx1); std::cout << "sx1.get is " << sx1.get() << std::endl; std::cout << "vs[0].get is " << vs[0].get() << std::endl; std::cout << "sx1.use_count is " << sx1.use_count() << std::endl; vs[0]->sayHi(); std::cout << "---------------------\n"; std::shared_ptr<X> sx2(new X(3)); std::cout << "sx2.get is " << sx2.get() << std::endl; vs.push_back(std::move(sx2)); std::cout << "sx2.get is " << sx2.get() << std::endl; std::cout << "vs[1].get is " << vs[1].get() << std::endl; std::cout << "vs[1].use_count is " << vs[1].use_count() << std::endl; vs[1]->sayHi(); std::cout << "---------------------\n"; }
结果如下:
ctor invoked: 2 sx1.get is 0x1abf010 vs[0].get is 0x1abf010 sx1.use_count is 2 HI: 2 --------------------- ctor invoked: 3 sx2.get is 0x1abf070 sx2.get is 0 vs[1].get is 0x1abf070 vs[1].use_count is 1 HI: 3 --------------------- dtor invoked: 3 dtor invoked: 2
6:shared_ptr实现了operator bool函数。也即是说,可以直接在if()中判断该shared_ptr是否拥有某个对象:
int main() { std::unique_ptr<int> ptr(new int(42)); if (ptr) std::cout << "before reset, ptr is: " << *ptr << ‘\n‘; ptr.reset(); if (ptr) std::cout << "after reset, ptr is: " << *ptr << ‘\n‘; else std::cout << "after reset, ptr is null\n"; }
结果如下:
before reset, ptr is: 42 after reset, ptr is null
7:推荐使用make_shared创建shared_ptr,而不是使用shared_ptr的构造函数。
一般情况下,shared_ptr内部会持有两个指针,一个是指向被管理对象的指针;另一个是指向控制块的指针。所谓控制块,就是记录引用计数等属性的数据结构。
当使用make_shared创建shared_ptr时,被管理对象以及控制块的内存是一起申请的;而用构造函数创建的shared_ptr,被管理对象和控制块的内存是分开申请的。所以,make_shared的效率更高一些。
make_shared主要做三件事:
申请被管理对象以及引用计数的内存;调用适当的构造函数初始化对象;返回一个shared_ptr。
例子如下:
int main() { std::shared_ptr<int> ptrint = std::make_shared<int>(2); std::shared_ptr<X> ptrX = std::make_shared<X>(3); cout << "*ptrint is " << *ptrint << endl; ptrX->sayHi(); }
结果如下:
ctor invoked: 3 *ptrint is 2 HI: 3 dtor invoked: 3
三:weak_ptr
shared_ptr的缺点在于不适用与循环引用的情况,这也是引入std::weak_ptr的原因。所谓循环引用,例子如下:
struct Child; struct Parent { shared_ptr<Child> child; ~Parent() { cout << "Bye Parent" << endl; } void hi() const { cout << "Hello" << endl; } }; struct Child { shared_ptr<Parent> parent; ~Child() { cout << "Bye Child" << endl; } }; int main() { auto parent = make_shared<Parent>(); auto child = make_shared<Child>(); parent->child = child; child->parent = parent; child->parent->hi(); }
上面代码的运行结果,只打印出”Hello”,而并没有打印出"Bye Parent"或"Bye Child",说明Parent和Child的析构函数并没有调用到。这是因为Parent和Child对象内部,具有各自指向对方的shared_ptr,加上parent和child这两个shared_ptr,说明每个对象的引用计数都是2。当程序退出时,即使parent和child被销毁,也仅仅是导致引用计数变为了1,因此并未销毁Parent和Child对象。
这种情况下,就可以使用weak_ptr来解决这种问题:
struct Child; struct Parent { shared_ptr<Child> child; ~Parent() { cout << "Bye Parent" << endl; } void hi() const { cout << "Hello" << endl; } }; struct Child { weak_ptr<Parent> parent; ~Child() { cout << "Bye Child" << endl; } }; int main() { auto parent = make_shared<Parent>(); auto child = make_shared<Child>(); parent->child = child; child->parent = parent; child->parent.lock()->hi(); }
上面的代码可以得到正确的结果:
Hello Bye Parent Bye Child
1:weak_ptr是对shared_ptr的封装,但是它不拥有被指向的对象,因而也就不会导致引用计数的变化。
可以使用shared_ptr对象构造weak_ptr,或对其进行赋值。表示该weak_ptr“共享”shared_ptr所拥有的对象。如果shared_ptr为空(不拥有任何对象),则该weak_ptr也是空的。
2:weak_ptr::use_count函数,返回当前有多少个shared_ptr实例共享被管理对象。
weak_ptr::expired函数检查被管理对象是否已经被销毁了,如果已经被销毁,则该函数返回true,否则返回false。expired函数要比use_count更快一些。例子如下:
struct Foo {}; int main() { std::weak_ptr<Foo> w_ptr; { auto ptr = std::make_shared<Foo>(); w_ptr = ptr; std::cout << "w_ptr.use_count() inside scope: " << w_ptr.use_count() << ‘\n‘; } std::cout << "w_ptr.use_count() out of scope: " << w_ptr.use_count() << ‘\n‘; std::cout << "w_ptr.expired() out of scope: " << std::boolalpha << w_ptr.expired() << ‘\n‘; }
结果如下:
w_ptr.use_count() inside scope: 1 w_ptr.use_count() out of scope: 0 w_ptr.expired() out of scope: true
3:weak_ptr所拥有的,是对shared_ptr所管理对象的弱引用,它必须临时转换为shared_ptr之后,才能临时性的访问引用对象。weak_ptr没有实现->操作符,相反,它实现了lock函数,该函数创建临时的shared_ptr,从而增加了它指向对象的引用计数,避免该对象被释放。
如果weak_ptr没有引用任何对象,则lock返回的shared_ptr对象也不引用任何对象,否则,lock返回的shared_ptr增加所引用对象的引用计数。
void observe(std::weak_ptr<int> weak) { if (auto observe = weak.lock()) { std::cout << "\tobserve() able to lock weak_ptr<>, value=" << *observe << "\n"; } else { std::cout << "\tobserve() unable to lock weak_ptr<>\n"; } } int main() { std::weak_ptr<int> weak; std::cout << "weak_ptr<> not yet initialized\n"; observe(weak); { auto shared = std::make_shared<int>(42); weak = shared; std::cout << "weak_ptr<> initialized with shared_ptr.\n"; observe(weak); } std::cout << "shared_ptr<> has been destructed due to scope exit.\n"; observe(weak); }
上面的代码中,首先构造一个空的weak_ptr对象weak,因此在observe函数中,weak_ptr::lock返回的shared_ptr也是空的;接下来在嵌套作用域中,首先构造了一个指向int类型的shared_ptr,然后用该shared_ptr对weak_ptr进行赋值,这样在observe函数中,weak_ptr::lock返回的shared_ptr也就临时拥有了该int类型对象;嵌套作用域结束后,其中的shared_ptr就被销毁了,weak又再次为空,所以observe函数中,weak_ptr::lock返回的shared_ptr又是空的了。结果如下:
weak_ptr<> not yet initialized observe() unable to lock weak_ptr<> weak_ptr<> initialized with shared_ptr. observe() able to lock weak_ptr<>, value=42 shared_ptr<> has been destructed due to scope exit. observe() unable to lock weak_ptr<>
四:shared_ptr中的控制结构
In a typical implementation, std::shared_ptr holds only two pointers:
the stored pointer (one returned by get());
a pointer to control block.
The control block is a dynamically-allocated object that holds:
either a pointer to the managed object or the managed object itself;
the deleter (type-erased);
the allocator (type-erased);
the number of shared_ptrs that own the managed object;
the number of weak_ptrs that refer to the managed object.
shared_ptr中包含两个指针,一个指向被管理的对象(也就是get()的返回值),另一个指向控制块。
控制块中包含:指向被管理对象的指针;deleter和allocator;引用计数。比如,shared_ptr<Foo> 的数据结构如下图所示,其中 deleter 和 allocator 是可选的:
The pointer held by the shared_ptr directly is the one returned by get(), while the pointer/object held by the control block is the one that will be deleted when the number of shared owners reaches zero. These pointers are not necessarily equal.
shared_ptr持有的指向被管理对象的指针,就是get()的返回值;而控制块中持有的指针,是当引用计数为0时,会被delete的指针,这两个指针的类型不一定相同,只要它们之间存在隐式转换。
这是 shared_ptr 的一大功能。分 3 点来说:
1: 无需虚析构
假设base 是 derived 的基类,但是 base 和 derived 都没有虚析构。
shared_ptr<derived > sp1(new derived ); // 控制块中指针的类型是 derived *
shared_ptr<base > sp2 = sp1; // 可以赋值,自动向上转型(up-cast)
sp1.reset(); // 这时 derived 对象的引用计数降为 1
此后 sp2 仍然能安全地管理 derived 对象的生命期,并安全完整地释放 derived ,因为其控制块记住了 derived 的实际类型。具体代码如下:
class base { public: base() {cout << "base ctor" << endl;} ~base() {cout << "base dtor" << endl;} }; class derived:public base { public: derived() {cout << "derived ctor" << endl;} ~derived() {cout << "derived dtor" << endl;} }; int main() { derived *pd = new derived; base *pb = pd; delete pb; cout << "---------------\n"; shared_ptr<derived> spd(new derived); shared_ptr<base> spb = spd; spd.reset(); }
对于原始指针而言,将指向derived的指针pd赋值给指向base的指针pb,在delete pb时,只会调用base的析构函数,而不会调用derived的析构函数。
而对于shared_ptr而言,使用指向derived的shared_ptr<derived> spd,初始化指向base的shared_ptr<base> spb,当超出作用域时,spb可以完整的释放 derived ,因为其控制块记住了 derived 的实际类型。
代码结果如下:
base ctor derived ctor base dtor --------------- base ctor derived ctor derived dtor base dtor
2:shared_ptr<void> 可以指向并安全地管理(析构或防止析构)任何对象;
shared_ptr<derived> sp1(new derived); // 控制块中指针的类型是 derived*
shared_ptr<void> sp2 = sp1; // 可以赋值,derived* 向 void* 自动转型
sp1.reset(); // 这时 derived对象的引用计数降为 1
此后 sp2 仍然能安全地管理 derived对象的生命期,并安全完整地释放 derived,不会出现 delete void* 的情况,因为 delete 的是控制块中的指针,不是 sp2.get()的返回值。
具体代码如下:
derived *pd = new derived; void *pv = pd; delete pv; cout << "---------------\n"; shared_ptr<derived> spd(new derived); shared_ptr<void> spv = spd; spd.reset();
对于原始指针而言,将指向derived的指针pd赋值给void类型的指针pv,在delete pv时,GCC会报编译警告,并且,实际运行时没有任何作用;
warning: deleting ‘void*’ is undefined [enabled by default]
而对于shared_ptr而言,使用指向derived的shared_ptr<derived> spd,初始化指向void的shared_ptr<void> spv,当超出作用域时,spv可以完整的释放 derived ,因为其控制块记住了 derived 的实际类型。
代码结果如下:
base ctor derived ctor --------------- base ctor derived ctor derived dtor base dtor
3. 多继承。
假设 base1 是 derived的多个基类之一,那么:
shared_ptr<derived> sp1(new derived);
shared_ptr<base1 > sp2 = sp1; //这时 sp1.ptr 和 sp2.ptr 可能指向不同的地址,因为
//base1 subobject 在 derived object 中的 offset 可能不为0。
sp1.reset(); // 此时 derived对象的引用计数降为 1
但是 sp2 仍然能安全地管理 derived对象的生命期,并安全完整地释放 derived,因为 delete 的不是 base1 *,而是原来的 derived*。换句话说,sp2中和控制块中的指针可能具有不同的值(当然它们的类型也不同)。
陈硕 http://blog.csdn.net/solstice/article/details/8547547
参考:
http://en.cppreference.com/w/cpp/memory
https://oopscenities.net/2013/04/09/smart-pointers-part-2-unique_ptr-2/