std::shared_ptr
std::shared_ptr是通过指针保持对象共享所有权的智能指针,多个std::shared_ptr引用对象占用同一个对象。当指针对象被另一个对象引用时可以把所有权共享给另一个引用指针对象,被管理的指针是当use_count抵达0是被删除。在下列情况之一出现时销毁对象并释放内存:
- 最后占有std::shared_ptr对象被销毁时;
- 最后占有std::shared_ptr对象被通过operator=或reset()赋值为另一个指针。
智能指针的本质
C++11在std::auto_ptr基础上新增了std::shared_ptr、std::weak_ptr智能指针,同时也修复了boot::make_ptr构造参数限制问题(boost::make_shared()不能接受任意多数量的参数来构造对象)。其实智能指针也并没有像它名字听起来那么神奇,它只是用对象去管理了一个资源指针,同时用一个计数器去计算当前指针引用对象的个数;当管理指针的对象增加或者减少时,计算器当前值也同步加1或减1,当最后一个指针对象被销毁时,计算器为1;此时,最后的指针对象被销毁(计算器抵达0)的同时也把指针管理对象的指针进行delete操作。
通过例子说明以上操作:
#include<iostream> #include <memory>
class Test { public: Test(){ std::cout << "Test()" << std::endl; } ~Test(){ std::cout << "~Test()" << std::endl; } };
int main(){ std::shared_ptr<Test> p1 = std::make_shared<Test>(); std::cout << "1 ref:" << p1.use_count() << std::endl; { std::shared_ptr<Test> p2 = p1; std::cout << "2 ref:" << p1.use_count() << std::endl;
std::shared_ptr<Test> p3 = p1; std::cout << "3 ref:" << p1.use_count() << std::endl; } std::cout << "4 ref:" << p1.use_count() << std::endl; system("pause"); return 0; } |
输出结果:
std::make_shared封装了new操作符,实现了对象的实例化。刚开始引用对象只有一个,因此打印1。当执行std::shared_ptr<Test> p2 = p1和std::shared_ptr<Test> p3 = p1,引用对象各自加1,一次打印2和3,当大括号结束,p2和p3生命周期结束,计算器执行两次减1操作,p1引用计算重新回到1,当函数运行完之时,p1会被销毁,此时计算器是1,就会调用p1的析构函数delete之前用std:: make_shared创建的指针。完成整个引用对象的管理。
std::shared_ptr解决了对象共享所有权的问题,那么它是不是完美的,有没有其他问题存在,答案是肯定的,如当两个类相互引用时就会产生对象释放异常的问题。
请看实例:
#include <iostream> #include <memory>
class B; class A{ public: A(){ std::cout << "class A : constructor" << std::endl; } ~A(){ std::cout << "class A : destructor" << std::endl; } void referB(std::shared_ptr<B> test_ptr) { _B_Ptr = test_ptr; } private: std::shared_ptr<B> _B_Ptr; };
class B{ public: B(){ std::cout << "class B : constructor" << std::endl; } ~B() { std::cout << "class B : destructor" << std::endl; } void referA(std::shared_ptr<A> test_ptr){ _A_Ptr = test_ptr; } std::shared_ptr<A> _A_Ptr; };
int main() { // test { std::shared_ptr<A> ptr_a = std::make_shared<A>(); //A引用计算器为1 std::shared_ptr<B> ptr_b = std::make_shared<B>(); //B引用计算器为1 ptr_a->referB(ptr_b); // B引用计算器加1 ptr_b->referA(ptr_a); // A引用计算器加1 } system("pause"); return 0; }
|
运行结果:
从输出结果可以看到,整个程序运行结束都没有看到A和B调用析构函数,这是怎么回事呢?
分析上面代码可知,std::shared_ptr<A>和std::shared_ptr<B>在接收std::make_shared实例化对象时,各自的引用计算都为1,而当执行ptr_a->referB(ptr_b)时,再次使用std::shared_ptr<B>,因此此时B的引用加1,即B的引用计算变成2;同理A的引用计算也变成2。当main函数退出前ptr_a和ptr_b引用计算均为2,main函数退出后引用计算均为1,构成相互引用。也就是会产生这样的情况:
ptr_a等待ptr_b释放自己,这样ptr_a才能去释放ptr_b。同理,ptr_b也等待ptr_a释放自己,这样ptr_b才能去释放ptr_a。
可见,相互引用导致了相互释放冲突的问题,最终导致内存泄露发生。
那用什么方法可以解决这个问题呢?
还有C++11同时提供了std::weak_ptr,利用std::weak_ptr就可以解决以上问题。
std::weak_ptr
#include <iostream> #include <memory>
class B; class A{ public: A() { std::cout << "class A : constructor" << std::endl; }
~A() { std::cout << "class A : destructor" << std::endl; }
void referB(std::shared_ptr<B> test_ptr) { _B_Ptr = test_ptr; } void print_refer() { std::cout << "refer A count : " << _B_Ptr.use_count() << std::endl; } void test_refer() { std::shared_ptr<B> tem_p = _B_Ptr.lock(); std::cout << "refer B : " << tem_p.use_count() << std::endl; } private: std::weak_ptr<B> _B_Ptr; };
class B{ public: B() { std::cout << "class B : constructor" << std::endl; }
~B() { std::cout << "class B : destructor" << std::endl; }
void referA(std::shared_ptr<A> test_ptr) { _A_Ptr = test_ptr; } void print_refer() { std::cout << "refer A count : " << _A_Ptr.use_count() << std::endl; } void test_refer() { std::shared_ptr<A> tem_p = _A_Ptr.lock(); std::cout << "refer A : " << tem_p.use_count() << std::endl; }
std::weak_ptr<A> _A_Ptr; };
int main(){ // test { std::shared_ptr<A> ptr_a = std::make_shared<A>(); //A引用计算器为1 std::shared_ptr<B> ptr_b = std::make_shared<B>(); //B引用计算器为1 ptr_a->referB(ptr_b); ptr_b->referA(ptr_a);
ptr_a->test_refer(); ptr_b->test_refer(); ptr_a->print_refer(); ptr_b->print_refer(); } system("pause"); return 0; } |
运行结果:
可以看到ptr_a和ptr_b在main退出前,引用计算均为1,也就是说在A和B中对std::weak_ptr的引用不会引起引用计算加1,ptr_a和ptr_b可以正常释放,不会引起内存泄露。
可见std::weak_ptr拥有弱引用特性,不拥有对象,只有等到调用lock()函数时才会有可能拥有对象,std::weak_ptr有以下特性:
- 只是拥有一个没有拥有的被std::shared_ptr托管的对象;
- 只有调用lock()创建std::shared_ptr时才会引用对象;
- 在lock()时会递增一个引用计算;
- 在std::shared_ptr主指针结束后,如果std::weak_ptr的lock成功对象还存在,那么此时还有代码调用lock的话,也会引起引用计算加1。