本章节主要关注于shared_ptr的一些问题;
基础问题:
首先注意一下shared_ptr和unique_ptr的区别和异同;
前者对于相同变量的会存有引用计数,允许多个shared_ptr指向同一个对象;
但是需要注意的是,同一个对象指的是某个具体的实例化对象;
和unique_ptr类似,shared_ptr也可以使用自定义析构器;
class widget {
public:
~widget() {
cout << "widget delete..." << endl;
}
};
auto slef_widget_delete = [](widget* pw) {
cout << "selfmake delete..." << endl;
delete pw;
};
int main() {
{
//test filed;
shared_ptr<widget>spw(new widget, slef_widget_delete);
}
return 0;
}
如下所示,值得注意的是,shared_ptr并不需要在模板实例化中告知是什么类型,这样就导致,同一类,针对于不同的实例化对象,可以定义不同的析构器,并且进行赋值移动;
换句话说,他们的类型也是相同的,不相同的只是对象堆内存中的数据结构,也就是控制块;
auto delete1 = [](widget* pw) {
cout << "selfmake 1 delete" << endl;
delete pw;
};
auto delete2 = [](widget* pw) {
cout << "selfmake 2 delete" << endl;
delete pw;
};
int main() {
shared_ptr<widget> pw1(new widget, delete1);
shared_ptr<widget> pw2(new widget, delete2);
vector<shared_ptr<widget>> vpw{ pw1,pw2 };
return 0;
}
底层实现问题:
造成shared_ptr最根源的问题在于底层实现;
和unique_ptr不同,shared_ptr的尺寸是裸指针的两倍,固定不变,不取决于有无自定义析构器;
从上述可以看出,针对于引用计数以及分配器,析构函数等,专门的放在了一个名叫控制块的地方;
该内存使用动态分配,并且计数等操作也依赖原子操作;
但是最关键的一点是,由于每个对象,都会拥有一个该控制块,并且所有shared_ptr共享,但是如果操作不当,可能会出现多个指向同一对象的shared_ptr拥有不同的控制块;
以下几种行为都会创建一个控制块:
- 使用make_shared会创建一个控制块;
- 从具有专属所有权的指针构造一个shared_ptr会构造一个控制块;
- 直接使用裸指针传入shared_ptr构造函数,会直接构造一个控制块;
因此,参照上述几条原则,最主要的原则为:
不要随随便便使用裸指针进行shared_ptr构造,应该使用工厂函数+new形式进行传参,后续如果需要共享,采用shared_ptr的复制函数进行构造;
对于本章后续,其实给出了一两个例子来论证该条的合理性;
考虑如下例子:
vector<shared_ptr<widget>> widget_list;
class widget {
public:
~widget() {
cout << "widget delete..." << endl;
}
void process() {
//do something....
widget_list.emplace_back(this);
}
};
process很符合逻辑,针对每个处理后的widget,之后加入到队列中;
但是这里还会有问题,一是违反了传递裸指针的规则,直接传了个*this进去,二是不能保证是否该实例化类是否在别的函数中已经有了shared_ptr指向;
因此可以使用一个专门的方式;
对于shared_ptr,给了一个类似于工厂函数,来返回标准的指向该类的shared_ptr;
class widget:public enable_shared_from_this<widget> {
public:
~widget() {
cout << "widget delete..." << endl;
}
void process() {
widget_list.emplace_back(this);
}
};
通过继承enable_shared_from_this<T>
模板,可以解决该问题;
该种继承方式称之为“奇妙递归模板模式”;
该模板定义了一个方式:
shared_from_this();
可以直接不构造控制块,返回一个指向该实例的shared_ptr;
因此避开了重复创建控制块的问题;
但是对于第一次创建控制块,则需要使用工厂函数模式:
vector<shared_ptr<widget>> widget_list;
class widget:public enable_shared_from_this<widget> {
public:
template<typename... Ts>
static shared_ptr<widget> create(Ts&&... params);
~widget() {
cout << "widget delete..." << endl;
}
void process() {
widget_list.emplace_back(this);
}
private:
//构造函数
};
使用该方式,即可使用create获得初始的shared_ptr,又可以使用shared_from_this()
来进行不创建块的shared_ptr创建;