本小节回顾学习的知识点分别是shared_ptr之、陷阱、性能分析、使用建议的详述。
今天总结的知识分为以下3个大点:
(1)std::shared_ptr使用陷阱分析
(1.1)慎用裸指针来构造shared_ptr
(1.2)慎用get()返回的指针
(1.3)不要把类对象指针(this)作为shared_ptr返回,改用
enable_shared_from_this;
(2)性能说明
(2.1)尺寸问题
(2.2)移动语义
(3)补充说明和使用建议
(1)std::shared_ptr的使用陷阱分析:
As we all know,shared_ptr会帮助我们自动管理在堆区heap中开辟的内存。非常好用,但正所谓万事万物有利必有弊!一旦用错shared_ptr的话,也会造成致命的后果!
(1.1)慎用裸指针来构造shared_ptr:
慎用理由1:凡是使用了裸指针来构造shared_ptr智能指针时,那么在后续的代码中。就不要再使用该裸指针了。因为会造成不安全的case!
例如以下场景:
void func(shared_ptr<int> ptr) {
return;
}
int* p = new int(100);//裸指针
func(shared_ptr<int>(p));
//这里传入func函数中的参数是一个临时的shared_ptr
//我用一个裸指针显式地构造这个临时的shared_ptr
//但是,这个裸指针p的内存在传入func后被释放了
//此时你无法对它的内存空间do事情了!
*p = 45;//×!此时p的内存空间已经被释放了!
调试结果:
裸指针p在传入func函数之前:
(p的地址:)
裸指针p在传入func函数并退出该函数之后:
(p的地址:)
下面这样使用裸指针来创建shared_ptr对象才是正确的做法:
void func(shared_ptr<int> ptr) {
return;
}
int* p = new int(100);//裸指针
shared_ptr<int> p2(p);//把裸指针绑定到shared_ptr上
func(p2);//传入一个shared_ptr指针,即:我把内存交给p2管理了!很好!
*p2 = 188;//ok!没问题的!安全的!
or
void func(shared_ptr<int> ptr) {
return;
}
shared_ptr<int> p2(new int(100));//把裸指针绑定到shared_ptr上
func(p2);//传入一个shared_ptr指针,即:我把内存交给p2管理了!很好!
*p2 = 188;//ok!没问题的!安全的!
慎用理由2:使用裸指针构造shared_ptr时候,一个裸指针只能绑定到一个shared_ptr中,不能绑定到多个shared_ptr中!
为什么呢?下面不妨请看以下代码:
int* p = new int(100);//裸指针
shared_ptr<int> p1(p);//把裸指针p绑定到p1上
shared_ptr<int> p2(p);//把裸指针p绑定到p2上
p1.reset();//释放shared_ptr指针p1
运行结果:
在执行p1.reset()这行代码之前的调试运行结果:
在执行p1.reset()这行代码之后的调试运行结果:
从调试结果可见,当一个裸指针被绑定到多个shared_ptr智能指针上时,就会出现一旦某个智能指针被释放时,别的绑定到该裸指针上的智能指针就会指向一个乱码的对象。(因为你已经释放了裸指针p了,p所指向的内存别的指向它的shared_ptr指针没有权限访问了)此外,如果别的指向裸指针的shared_ptr指针释放时,还会导致另外一个问题:重复释放同一内存空间。这会导致你的程序产生异常!
综上所述,慎用裸指针绑定到shared_ptr指针上!!!
(1.2)慎用get()返回的指针:
.get()函数都作用:返回智能指针指向的对象所对应的裸指针。
(因为有些库函数需要你传入的是裸指针而不是智能指针所以C++11才引入shared_ptr的这种方法!)
请看以下代码:
shared_ptr<int> p1(new int());
shared_ptr<int> p2(p1);
auto pp = p1.get();
调试结果:
慎用理由:对shared_ptr智能指针用.get()方法得到的裸指针你千万不能随意delete!因为这个权限你要交给shared_ptr去管理!(若你随意delete后,shared_ptr就没办法帮助我们正常管理该内存了)
请看以下代码:
shared_ptr<int> p1(new int());
shared_ptr<int> p2(p1);
auto pp = p1.get();
delete pp;//错误!你delete了,那还用智能指针shared_ptr帮你管理内存干嘛?
shared_ptr<int> p1(new int());
auto pp = p1.get();
{
shared_ptr<int> p3(pp);
}
//相当于将裸指针pp既绑定到shared_ptr指针p1上,也绑定到p3上了
//这和前面的裸指针被绑定到多个shared_ptr指针上产生异常的case是一样的!
调试运行结果:
在{shared_ptr<int> p3(pp); }这个大括号语句内的代码执行之前的调试结果:
(pp的地址:)
在{shared_ptr<int> p3(pp); }这个大括号语句内的代码执行之后的调试结果:
(pp的地址:)
从调试结果可见,当执行完{shared_ptr<int> p3(pp); }codes之后pp的内存就已经给释放掉了,当后续shared_ptr指针p1再自动帮我们释放内存时又会产生重复delete的异常问题!
综上所述,永远不要将shared_ptr中的方法.get()得到的裸指针绑定(赋值给/初始化给)到别的shared_ptr指针上!!!
(1.3)不要把类对象指针(this指针)作为shared_ptr返回,改用enable_shared_from_this:
当你在类中将this指针绑定给shared_ptr时,又在类的外部将该shared_ptr给别的shared_ptr指针做初始化的话,就又会造成上述使用裸指针时出现的问题:用一个裸指针绑定到多个shared_ptr上时,会造成重复释放用一份内存空间的异常问题!
请看以下代码:
class CT {
public:
shared_ptr<CT>getself() {
return shared_ptr<CT>(this);//<===> 用裸指针初始化了多个shared_ptr!
}
};
shared_ptr<CT> pct1(new CT());
shared_ptr<CT> pct2 = pct1->getself();
运行结果:
那么如何解决在类中用this指针给shared_ptr初始化时的这个问题呢?
答:用C++标准库里面的类模板:enable_shared_from_this来deal这个问题。
请看以下代码:
class CT:public enable_shared_from_this<CT> {
public:
shared_ptr<CT>getself() {
return shared_from_this();
//通过模板类enable_shared_from_this中的方法shared_from_this将this指针用作
//shared_ptr指针来do返回!
}
};
shared_ptr<CT> pct1(new CT());
shared_ptr<CT> pct2 = pct1->getself();
正常运行~
(2)性能说明:
(2.1)尺寸(占据的内存)问题:
shared_ptr的尺寸是裸指针的2倍,weak_ptr尺寸也是裸指针的2倍。与weak_ptr一样,shared_ptr也是含有2个裸指针的。
a)第一个裸指针指向的是这个智能指针所指向的对象
b)第二个裸指针指向的是一个很大的数据结构-->控制块(由make_shared()函数来创建),这个控制块内含有:
b.1)所指向对象的强引用计数
b.2)所指向对象的弱引用计数
b.3)其他data是:删除器指针、内存分配器等等。
(2.2)移动语义:
用std::move()函数do移动赋值,可直接让别的对象的内存访问权限直接给到我要do移动的对象。
请看以下代码:
shared_ptr<int> sp1(new int(1));
shared_ptr<int> sp2(std::move(sp1));
//移动语义:移动构造一个新的智能指针对象
//此时,sp1就不再指向该int型的val==1的对象了(变为空)
//引用计数依旧为1
shared_ptr<int> sp3(std::move(sp2));
//此时sp2就变为空,但其引用计数仍然为1
//用std::move()函数移动赋值肯定比单纯的赋值块:因为单纯的赋值时你还需要增加引用计数,但是移动则不需要!
(3)补充说明和使用建议:
关于shared_ptr的知识我上面总结的差不多了。还有一个分配器的概念没总结。
分配器:解决内存分配的问题。
shared_ptr<int> p(new int(),mydeleter(),mymallocator<int>());...
这里就不详细讲了,因为分配器用得地方很少!!!
建议:虽然shared_ptr<T>智能指针能帮助我们管理资源,但一定要细心地使用,谨慎地使用!虽然make_shared<T>()函数不能用自定义的删除器,但我们仍要优先使用make_shared<T>()函数来创建shared_ptr<T>智能指针!
注意:即便是C++17标准之后,shared_ptr也无法配合make_shared来do动态分配数组的工作,但是unique_ptr已经可以配合make_unique或者new来do动态分配数组的工作了!
const int N = 5;
//这3行codes完全没有问题,可编译通过并正常运行!
unique_ptr<int[]> unpt = make_unique<int[]>(N);
for (int i = 0; i < N; i++) unpt[i] = i;
for (auto i = 0; i < N; i++)cout << unpt[i] << "\t";
//这3行codes看起来也没有问题,但就是编译不能通过!更别说正常运行了!
shared_ptr<int[]> spt = make_shared<int[]>(N);
for (int i = 0; i < N; i++) spt[i] = i;
for (auto i = 0; i < N; i++)cout << spt[i] << "\t";
unique_ptr这3行codes的运行结果:
shared_ptr这3行codes的运行结果:
可见,shared_ptr就目前而言还是有缺陷的!当然,既然是共享式的smartPointer,那么它指向一个数组后,这个数组已经可以存放很多data了,没有必要再让别的同该类的shared_ptr再执行该数组了,王老师他个人认为这应该是C++标准委员会目前不让shared_ptr拥有这种能力的缘故吧!而unique_ptr则无此顾虑, 因为unique_ptr本来就是独占式的,赋初值后别人“碰都别想碰”!
以上就是我总结的关于shared_ptr之、陷阱、性能分析、使用建议的详述的笔记。希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~