上一次已经谢了一些东西了,感觉总结的差不多了,这一期主要是上一期的查漏补缺。主要是侧重回答一些比较重一些的问题,比如智能指针、RAII机制还有最长被问到的C++的多态。
首先是智能指针。
上一期介绍一下智能指针在用法的上要注意的部分,这次主要介绍一下为什么。
这里要提及的首先有三点:
使用如下代码:
#include <memory>
#include <iostream>
using namespace std;
int main()
{
int *rpw = new int(12);
{
shared_ptr<int> isptr1(rpw);
cout << sizeof(rpw) << endl;
cout << sizeof(isptr1) << endl;
}
system("pause");
return 0;
}
在32bit的电脑上可以得到结果:
明显看到验证了第一条。为什么会是两倍具体原因后面会分析
与unique_ptr类似,shared_ptr使用delete作为默认的资源析构函数,但是也可以使用用户自己提供的删除函数(deleter)。不过与unique_ptr不同的是,(unique_ptr的deleter是unique_ptr的类型的一部分)shared_ptr的deleter的类型不再是shared_ptr的类型的一部分。如下示例:
auto del1 = [](int *p) {
cout << "del1" << endl;
delete p;
};
auto del2 = [](int *p) {
cout << "del2" << endl;
delete p;
};
shared_ptr<int> isptr1(new int(12), del1);
shared_ptr<int> isptr2(new int(10), del2);
vector<shared_ptr<int>> vsptr{ isptr1,isptr2 };
这里的isptr1和isptr2的类型是一样的,而对于unique_ptr则不同:
auto del1 = [](int *p) {
cout << "del1" << endl;
delete p;
};
auto del2 = [](int *p) {
cout << "del2" << endl;
delete p;
};
unique_ptr<int, decltype(del1)> iuptr1(new int(12), del1);
unique_ptr<int, decltype(del2)> iuptr2(new int(10), del2);
这里的iuptr1和iuptr2是两种不同的类型。
回到上面的分析中,为什么shared_ptr的大小是raw pointer的两倍的呢?原因主要是在shared_ptr的内部不只有一个类似原始指针的指向object的指针,还有一个指向Control Block的指针:
如上所示,清晰的看到shared_ptr的sizeof返回值应该是2个指针的大小,其中一个指针指向需要指向的object,另外一个指针指向Control Block。Control Block中包含了对这个shared_ptr控制所必需的一些信息,包括引用计数Reference Count、Weak Count、以及在Other Data中会存放用户指定的deleter函数,分配器(allocator)等。所以从shared_ptr的开销角度来说,接下主要是讨论Control Block的创建已经创建带来的问题和Control Block的开销。
一般来说在三种情况下会创建Control Block
上面三种情况都会创建Control Block,但是问题就出在这个Control Block上,稍后讨论该部分的开销。如果使用同一个raw pointer创建shared_ptr就会出现两个不同的shared_ptr指向同一个raw pointer指向的资源,但是有两个不同的Control Block,当一个的引用计数为0的时候,就会调用deleter释放该资源,那么当另一个shared_ptr也要释放该资源的时候就会发生释放已经被释放的资源的错误,如下所示:
int *rpw = new int(12);
{
shared_ptr<int> isptr1(rpw);
shared_ptr<int> isptr2(rpw);
}
显然这种错误是致命的,因为有可能是发生在析构函数中,接下来还会导致资源泄漏,原本我们是为了防止资源泄漏的。所以,这里建议尽我们在使用shared_ptr的时候应当避免使用raw pointer创建shared_ptr,也就是尽量避免。
RAII是Resource Acquisition Is Initialization的缩写,也有人称其为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。
(稍后补全)