前言-
在现代C++编程中应该尽量避免使用裸指针,裸指针很容易带来诸如内存泄露、内存越界、未定义行为等难以察觉的错误。
C++11中共有4种智能指针:std::auto_ptr、std::unique_ptr、std::shared_ptr和std::weak_ptr。所有这些智能指针都是为管理动态分配对象的生命期而设计的,通过保证这样的对象在适当的时机以适当的方式析构来防止资源泄露。
一、auto_ptr
1、简介
auto_ptr是C++11中已经废弃了的特性,目前已经被unique_ptr代替。除了在需要使用C++98编译器来编译代码的场合,其他情况下均可用unique_ptr来代替。
2、特性
(1)对auto_ptr对象执行复制操作会将其值置空,也就是,同一时刻只能有一个auto_ptr对象管理某一资源。
(2)不能在容器中存储auto_ptr对象
3、实例
auto_ptr<int> ptr1(new int(5));
cout << *(ptr1.get()) << endl;//输出5
auto_ptr<int> ptr2 = ptr1;
cout << *(ptr2.get()) << endl;//输出5
if (nullptr == ptr1.get())
{
cout << "ptr1 is null" << endl; //输出ptr1 is null
}
vector<auto_ptr<int>> v_test;
v_test.push_back(ptr2); //报错 error C2558: class“std::auto_ptr<int>”: 没有可用的复制构造函数或复制构造函数声明为“explicit
二、unique_ptr
1、简介
unique_ptr能做auto_ptr能够做到的任何事情并且执行效率和auto_ptr一样高,几乎和裸指针有着相同的尺寸。std::unique_ptr实现的是专属所有权语义。一个非空的std::unique_ptr总是拥有其所指涉的资源。
2、特性
(1)默认情况下析构函数通过delete运算符实现,但也可以自定义析构器(一般情况下用不到)。如果自定义析构器,unique_ptr的尺寸一般会增一两个字,但是如果析构器是一个函数对象且函数对象包含很多状态,那尺寸将会大大地增加。
(2)C++11中不允许从裸指针隐式转换为智能指针。
(3)auto_ptr不支持传入deleter,所以只能支持单对象(delete object),而unique_ptr对数组类型有偏特化重载,并且还做了相应的优化,比如用[]访问相应元素等.
(4)unique_ptr可以进行移动构造和移动赋值操作。
3、实例
int value = 5;
int * ptr_value = &value;
//unique_ptr<int> uptr = ptr_value; //错误
unique_ptr<int> ptr1(new int(5));
cout << *ptr1.get() << endl; //输出5
//unique_ptr<int> ptr2 = ptr1;//错误
unique_ptr<int>ptr2 = std::move(ptr1);
cout << *ptr2.get() << endl; //输出5
if (nullptr == ptr1.get())
{
cout << "ptr1 is null" << endl; //输出ptr1 is null
}
三、shared_ptr
1、简介
shared_ptr采用共享所有权来管理资源,当最后一个指涉到资源的std::shared_ptr不再指涉它时,std::shared_ptr会析构其指涉的对象。正如垃圾回收一样,用户无须操心如何管理被指涉的资源的生存期。shared_ptr采用引用计数机制来确定合适回收资源。
2、特性
(1)std::shared_ptr的尺寸是裸指针的两倍。因为它内部即包含一个指涉到该资源的裸指针,耶包含一个指涉到该资源的引用计数的裸指针。
(2)引用计数的内存动态分配。引用计数的递增和递减是原子操作。
(3)支持复制和移动。但是移动构造函数比复制构造函数快,移动赋值比复制赋值快。
(4)shared_ptr也支持自定义析构器,而且针对同一类型的对象,可以拥有不同的析构器。但是为什么shared_ptr的尺寸总是相当于裸指针的二倍呢?
因为shared_ptr包含有两个指针,一个指针指向所管理的对象,另一个指针指向一个控制块,控制块在堆上分配。控制块包含引用计数、弱计数、自定义删除器、分配器以及其他数据。
(5)尽可能避免将裸指针传递给一个shared_ptr的构造函数。因为传递裸指针可能导致所指资源被删除多次。
3、实例
shared_ptr<int> ptr1(new int(5));
cout << *ptr1.get() << endl; //输出5
shared_ptr<int> ptr2 = ptr1;
cout << *ptr2.get() << endl; //输出5
if (nullptr == ptr1)
{
cout << "ptr1 is null" << endl;
}
else
{
cout << "ptr1 is not null" << endl; //输出ptr1 is not null
}
四、weak_ptr
1、简介
weak_ptr能够像std::shared_ptr一样方便,但是无须参与管理所指涉的对象的共享所有权。这种指针既能像std::shared_ptr那样运作,但又不影响其指涉对象的引用计数。
2、特性
(1)weak_ptr不能提领,也不能检查是否为空。这是因为weak_ptr并不是一种独立的只能指针,而是std::shared_ptr的一种扩充。
(2)可以通过检验weak_ptr是否失效来决定是否要访问所指涉到的对象。
(3)weak_ptr可能的用武之地包括缓存、观察者列表、以及避免std::shared_ptr指针环路。
3、实例
class A
{
public:
A() { cout << "构造" << endl; };
~A() { cout << "析构" << endl; }
};
int main()
{
auto spa = std::make_shared<A>();//A被构造
std::weak_ptr<A> wpa(spa);
spa = nullptr;//A 被析构
if (wpa.expired())
{
cout << "wpa 不再指涉任何对象" << endl;
}
std::shared_ptr<A> spa2 = wpa.lock(); //如果wpa空悬,则spa2为空
}
五、std::make_unique和std::make_shared
1、简介
make_unique和make_shared分别用来初始化智能指针使用。应该优先使用两者初始化智能指针,而非直接使用new。
原因右下:
(1)消除重复代码
(2)异常安全。
void processWidget(std::shared_ptr<Widget>(new Widget), doSomething());//如果doSomething抛出异常,可能会导致内存泄露
void processWidget(std::make_shared<Widget>(), doSomething());//而使用make_shared不会有内存泄露的风险
(3)make_shared只包含一次内存分配调用,把控制快和指涉对象的内存分配到一起了。但是也导致了通过对应于std::shared_ptr的make系列函数所分配的内存在最后一个std::shared_ptr和最后一个指涉到它的std::weak_ptr都被析构之前,无法得到释放。
(4)不适用于make系列函数的场景包括:需要定制删除器、期望直接传递大括号初始化物。
(5)对于std::shared_ptr,不建议使用make系列函数的额外场景包括:[1]自定义内存管理的类【2】内存紧张的系统、非常大的对象以及存在比指涉到相同对象的std::shared_ptr生存期更久的std::weak_ptr。
参考文献:《effective modern C++》第四章 智能指针