智能指针是C++11新引入的行为类似于指针的类对象,智能指针最大的特点是可以帮助管理动态内存分配的智能指针模板。
如果在一个类中定义了指针,我们知道在这个类不再使用时编译器会自动调用其析构函数以释放内存,所以该指针所占用的内存也会被释放。然而,该指针所指向的内存不会被释放,从而造成内存泄露。
解决的办法之一是在类的析构函数中写上所有动态分配内存的释放代码。而C++11的智能指针为我们提供了一种新的解决问题的办法:当类的析构函数被调用时,我们知道这个类中所声明的其它类型对象的析构函数也会被调用,从而释放其它对象内存。我们也想用这个功能来管理指针,然后指针只是指针,不是对象。所以C++11提供了对于指针的包装——智能指针。
C++提供了三种智能指针类型:auto_ptr,unique_ptr,shared_ptr
三种类型的智能指针的定义方式都类似与下面这种:
std::auto_ptr<std::string> ps(new std::string("str"));
而ps
的使用方式也和普通指针一样。
现在一般不建议使用auto_ptr
,编译使用了auto_ptr
的代码编译器甚至会给出警告,为什么呢?
对于下面的语句:
auto_ptr<string> ps(new string("str"));
auto_ptr<string> vocation;
vocation = ps;
如果编译运行这段代码会发现有 "free(): invalid pointer" 的报错。这是因为,这样会有两个智能指针指向同一个string对象,就会导致两次string对象的释放。
解决这样的办法有多种策略:
=
运算符,使得=
时会自动构造一个副本;vocation = ps
时vocation
将剥夺ps
对于string对象的指针所有权。保证只有一个智能指针对象管理一个指针。这就是unique_ptr
的策略;shared_ptr
的策略。但是如果你使用unique_ptr
重新写一遍上述代码时,发现还是无法实现。这时因为虽然vocation
虽然接管了string对象的所有权,但是ps
还是有可能使用,而ps
不再指向有效的数据,带来了潜在的风险。
因此,编译器规定:如果等式的右端是一个右值的话,这种操作是成立的,因为右值是临时的,很快就会被销毁,不会被别人使用。如果是左值的话,这种操作将不被允许。
注意:使用 new 分配内存时,才能使用auto_ptr
(当然这个最好也不要用)和shared_ptr
,使用 new[] 分配内存时,要使用unique_ptr
。
下面各种观点是从各种博客和知乎回答搜集而来
首先如果是全局对象的话,完全可以使用纯指针,反正在程序运行期间内存都不会被释放。
对于unique_ptr
,其内存几乎与纯指针一样大。但是不能被复制,如果对象不需要创建副本的话,尽量使用unique_ptr
。
对于shared_ptr
,其使用计数器,可以多个变量共享指针。缺点是内存起码是纯指针的两倍。同时在多线程的情况下,在计数器的操作上涉及到原子操作,增加CPU的负荷。
知乎有这样一个问题:C++ 的智能指针不就基本解决了野指针问题了吗?为什么还要吹捧rust的内存安全?
我从各个回答总结出以下几点:
要非常清楚智能指针的生命周期
或者说,要尽量遵循RAII的设计规范:对于已经被封装的东西,在封装范围之外尽量使用封装的对象。
比如下面的这个例子:
void process(std::shared_ptr<int> svp) {}
int main(int argc, char** argv) {
int* vp = new int(10);
process(std::shared_ptr<int>(vp));
std::cout << *vp << std::endl; // pointer "vp" has already been released.
return 0;
}
/*
作者:Jason于航
链接:https://www.zhihu.com/question/400093693/answer/1270543164
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
这个例子中使用智能指针基本只作为临时变量,因此当该临时变量被回收时,指针指向的内存也一齐被回收了。
这个既可以说是不清楚智能指针的生命周期,也可以说不遵循封装对象的使用规范。
循环引用问题
所有通过计数器进行内存回收的算法都免不了一个问题:循环引用。
所以shared_ptr
也存在这个问题,在使用时就要特别注意。
不当用法造成的潜在内存泄露
bool complicatedCompute() { /* ... */ return true; } // potential memory leak;
auto process(std::shared_ptr<int>, bool) {}
int main(int argc, char** argv) {
process(std::shared_ptr<int>(new int(10)), complicatedCompute());
return 0;
}
/*
作者:Jason于航
链接:https://www.zhihu.com/question/400093693/answer/1270543164
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
这个问题在《Effective C++》一书中也提到过,可能一开始还难以看出问题。问题出在process
函数的参数上:因为这个传参不是直接传入一个变量,中间还要进行多步操作,每一步都有可能产生异常。造成动态内存的分配和与智能指针的绑定之间存在隔阂,带来潜在的内存泄露。
暂时就补充这么多吧,以后可能会继续更新,毕竟C++内存安全实在防不胜防。
原文:https://www.cnblogs.com/lunar-ubuntu/p/13731978.html