智能指针
一、shared_ptr
shared_ptr采用的是共享所有权来管理所指向对象的生存期,它提供引用计数机制,记录有多少个shared_ptr指向同一块动态内存空间,只有最后一个shared_ptr被销毁时,才会自动释放该内存。
1、快速使用shared_ptr
#include <iostream>
#include <memory>
using namespace std;
class MyClass {
public:
MyClass() {
cout << "MyClass constructed!" << endl;
}
~MyClass() {
cout << "MyClass destoryed!" << endl;
}
};
int main() {
shared_ptr<MyClass> sp1(new MyClass());
shared_ptr<MyClass> sp2(sp1);
cout << sp1.get() << " :" << sp1.use_count() << endl; // 2
cout << sp2.get() << " :" << sp2.use_count() << endl; // 2
return 0;
}
上面代码中,sp1和sp2均为shared_ptr,sp1是通过new运算符返回的地址来构造出来的,它管理MyClass()对象;sp2通过拷贝构造函数,和sp1共同管理同一个对象的内存。因此,它们的使用的是同一个控制块,引用计数器的数值为2。
2、为何经常使用make_shared来创建一个shared_ptr,而不是直接使用shared_ptr的构造函数呢?
使用make_shared相比于直接使用shared_ptr的构造函数主要有两个优势:① 内存分配效率高、② 异常安全。
①、内存分配效率高
使用shared_ptr的构造函数创建一个shared_ptr对象需要分配两次内存:
shared_ptr<MyClass> sp1(new MyClass());
上述代码中,首先为MyClass分配一次内存,然后为控制块(包含引用计数器等)分配一次内存,如下图所示:
而使用make_shared的话,仅需要一次分配内存:
shared_ptr<MyClass> sp1 = make_shared<MyClass>();
上述代码中,将一次性为MyClass和控制块分配内存,如下图所示:
②、异常安全
下面这种情况下,使用make_shared不会造成内存泄漏,而使用shared_ptr的构造函数会造成内存泄漏:
func(shared_ptr<int>(new int(12)), shared_ptr<int>(new int (20)))
由于C++是不保证参数求值顺序的,所以可能是下面这种顺序:
new int(12)
new int(20)
shared_ptr
shared_ptr
上述顺序中,如果第二步new int(20)出现了异常,将导致第一步new int(12)分配的内存没有被shared_ptr管理,从而导致内存泄漏,而如果使用make_shared则可以避免这种情况。因为make_shared为管理对象和控制块分配内存是一起的,一旦管理对象的内存分配完毕,一定会有一个控制块来管理它。
make_shared的使用方法如下:
func(make_shared<int>(12), make_shared<int>(20))
二、weak_ptr
1、快速使用weak_ptr
#include <iostream>
#include <memory>
using namespace std;
class MyClass {
public:
MyClass() {
cout << "MyClass constructed!" << " - - " << this << endl;;
}
~MyClass() {
cout << "MyClass destoryed!" << endl;
}
};
int main() {
shared_ptr<MyClass> sp1 = make_shared<MyClass>();
weak_ptr<MyClass> wp1(sp1);
return 0;
}
2、weak_ptr是如何帮助shared_ptr解决循环引用问题的?
下面代码中,发生了shared_ptr循环引用的情况,导致无法正常调用析构函数。
#include <iostream>
#include <memory>
using namespace std;
class YourClass;
class MyClass {
public:
MyClass() {
std::cout << "MyClass constructed!" << endl;
}
~MyClass() {
std::cout << "MyClass destoryed!" << endl;
}
void set_ptr(std::shared_ptr<YourClass>& ptr) {
m_ptr_your = ptr;
}
private:
shared_ptr<YourClass> m_ptr_your;
};
class YourClass {
public:
YourClass() {
std::cout << "YourClass constructed!" << endl;
}
~YourClass() {
std::cout << "YourClass destoryed!" << endl;
}
void set_ptr(std::shared_ptr<MyClass>& ptr) {
m_ptr_my = ptr;
}
private:
shared_ptr<MyClass> m_ptr_my;
};
int main()
{
shared_ptr<MyClass> ptr_my(new MyClass());
shared_ptr<YourClass> ptr_your(new YourClass());
ptr_my->set_ptr(ptr_your);
ptr_your->set_ptr(ptr_my);
return 0;
}
代码运行结果如下:
MyClass constructed!
YourClass constructed!
从运行结果可以看出,该代码没有正确调用析构函数。
循环引用的情况如下图所示:
如果将MyClass中的m_ptr_your的指针类型从shared_ptr改成weak_ptr,那么它将打破循环引用的问题,从而正确调用析构函数。修改代码后运行结果如下:
MyClass constructed!
YourClass constructed!
YourClass destoryed!
MyClass destoryed!
从运行结果可以看出,由于m_ptr_your使用的是weak_ptr指针,因此YourClass将会先调用析构函数,然后是MyClass调用析构函数。
三、unique_ptr
unique采用的是独占所有权来管理所指向对象的生存期。也就是说,同一时刻,只能有一个unique_ptr指针指向这个对象,当这个unique_ptr被销毁的时候,它所指向的对象也会被销毁。
1、快速使用unique_ptr
#include <iostream>
#include <memory>
using namespace std;
class MyClass {
public:
MyClass() {
std::cout << "MyClass constructed!" << endl;
}
~MyClass() {
std::cout << "MyClass destoryed!" << endl;
}
};
int main()
{
unique_ptr<MyClass> up1(new MyClass());
unique_ptr<MyClass> up2(up1); // 不允许,unique_ptr禁用了拷贝构造函数
unique_ptr<MyClass> up3 = up1; // 不允许,unique_ptr禁用了重载赋值运算符
return 0;
}