使用智能指针的注意事项

来源:互联网 发布:赌神2016网络电影 编辑:程序博客网 时间:2024/05/18 06:22

1. 使用unique_ptr以替代auto_ptr

auto_ptr是C++98标准库提供的一个智能指针,但已被C++11明确声明不再支持。auto_ptr具有以下缺陷:
* auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()进行转移。

#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    auto_ptr<A> auto_ap(new A("auto_ptr")),auto_bp;    cout<<auto_ap.get()<<endl;    auto_bp = auto_ap;    cout<<auto_ap.get()<<endl;    unique_ptr<A> unique_ap(new A("unique_ptr")),unique_bp;    cout<<unique_ap.get()<<endl;//    unique_bp = unique_ap;  // 报错    unique_bp = move(unique_ap);    cout<<unique_ap.get()<<endl;    return 0;}

运行结果:

auto_ptr:构造函数0x6115d00unique_ptr:构造函数0x6115f00unique_ptr:析构函数auto_ptr:析构函数
  • auto_ptr不可作为容器元素,unique_ptr可以作为容器元素。因为auto_ptr的拷贝和赋值具有破坏性,不满足容器要求:拷贝或赋值后,两个对象必须具有相同值。
  • auto_ptr不可指向动态数组,unique_ptr可以指向动态数组。因为unique_ptrunique_ptr<T[]>重载版本,销毁动态对象时调用delete[]
#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {//    auto_ptr<A[]> auto_ap(new A[1]{A("unique_ptr")});  // 报错    unique_ptr<A[]> unique_ap(new A[1]{A("unique_ptr")});    return 0;}

运行结果:

unique_ptr:构造函数unique_ptr:析构函数
  • auto_ptr不可以自定义删除器deleter,而unique_ptr可以。
#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    unique_ptr<A,void(*)(A*)> unique_ap(new A[2]{A("unique_ptr0"),A("unique_ptr1")},                                          [](A *a){                                              delete []a;                                          });    return 0;}

运行结果:

unique_ptr0:构造函数unique_ptr1:构造函数unique_ptr1:析构函数unique_ptr0:析构函数

2. 尽量使用unique_ptr而非shared_ptr

默认情况下,应使用unique_ptr,理由如下:

  • 若使用unique_ptr,当需要共享对象所有权时,依然可以将其转化为shared_ptr,但反过来则不行。
  • 使用shared_ptr需要消耗更多的资源,shared_ptr需要维护一个指向动态内存对象的线程安全的引用计数器以及背后的一个控制块,这使它比unique_ptr更加复杂。
  • 共享对象所有权也许并非你的本意,但使用shared_ptr有可能造成其他程序员无意间通过赋值给另一个共享指针而修改了你共享出来的对象。
#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    unique_ptr<A> a(new A("unique_ptr"));    shared_ptr<A> b = move(a);//    a = move(b);  // 报错//    a.reset(b.get());  // 运行错误    cout<<a.get()<<endl;    return 0;}

运行结果:

unique_ptr:构造函数0unique_ptr:析构函数

3. 谨慎使用裸指针

当需要裸指针与智能指针搭配使用时,需要避免如下操作:

  • 使用裸指针初始化多个智能指针
  • 对智能指针使用的裸指针执行delete操作

以上操作会导致程序再次尝试销毁已被销毁了的对象,进而造成程序崩溃。所以,当使用裸指针初始化智能指针后,应确保裸指针永远不应该被再次使用。

#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    A *pa = new A("ptrA");    unique_ptr<A> unique_pa(pa);//    delete pa;  // 运行错误    A *pb = new A("ptrB");    unique_ptr<A> unique_pb1(pb);//    unique_ptr<A> unique_pb2(pb);  // 运行错误    return 0;}

4. 不要使用静态分配对象的指针初始化智能指针

不要使用静态分配对象的指针初始化智能指针,否则,当智能指针本身被撤销时,它将试图删除指向非动态分配对象的指针,导致未定义的行为。

#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};A a("全局变量");int main() {    A b("局部变量");//    unique_ptr<A> pa(&a); // 运行错误    unique_ptr<A> pa(&b);    return 0;}

运行结果:

全局变量:构造函数局部变量:构造函数局部变量:析构函数局部变量:析构函数全局变量:析构函数

5. unique_ptr可以作为函数返回值

尽管unique_ptr无拷贝语义,但提供了移动语义,所以可作为函数返回值。

#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};unique_ptr<A> fun(){    cout<<"==>fun()"<<endl;    unique_ptr<A> pa(new A("unique_ptr"));    cout<<"<==fun()"<<endl;    return pa;}int main() {    cout<<"==>main()"<<endl;    auto pa = fun();    cout<<"<==main()"<<endl;    return 0;}

运行结果:

==>main()==>fun()unique_ptr:构造函数<==fun()<==main()unique_ptr:析构函数

6. 谨慎使用智能指针的getrelease方法

当使用get方法返回裸指针时,智能指针并没有释放指向对象的所有权,所以我们必须小心使用裸指针以避免程序崩溃。

但通过unique_ptr.release()方法返回的裸指针,需要我们自己delete删除对象,因为调用release方法后,该unique_ptr不再拥有对象的所有权。

#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    unique_ptr<A> unique_pa(new A("unique_ptr"));    A *pa = unique_pa.get();//    delete pa; // 运行错误//    unique_ptr<A> unique_pb(pa); // 运行错误    A *pc = unique_pa.release();    delete pc;    return 0;}

7. 在对象内部获取shared_ptr必须使用shared_from_this方法

在对象内部如果想要获取指向该对象的shared_ptr,不可以使用this指针进行构造(理由见第3点),而必须使用shared_from_this方法,以确保所有的shared_ptr指向同一个控制块。

  • 未使用shared_from_this情况:
#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    shared_ptr<A> get_shared_ptr(){return shared_ptr<A>(this);}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    shared_ptr<A> pa(new A("shared_ptr"));//    shared_ptr<A> pb = pa->get_shared_ptr(); // 运行错误    return 0;}
  • 使用shared_from_this情况:
#include <iostream>#include <memory>using namespace std;class A:public enable_shared_from_this<A>{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    shared_ptr<A> get_shared_ptr(){return shared_from_this();}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    shared_ptr<A> pa(new A("shared_ptr"));    cout<<"use count = "<<pa.use_count()<<endl;    shared_ptr<A> pb = pa->get_shared_ptr();    cout<<"use count = "<<pa.use_count()<<endl;    return 0;}

运行结果:

shared_ptr:构造函数use count = 1use count = 2shared_ptr:析构函数

8. 谨慎使用shared_from_this方法

当需要使用shared_from_this方法时,应注意以下几点:

  • 不可以在构造函数中调用shared_from_this方法

在下面的代码中,shared_ptr<A> shared_pa(new A("shared_ptr"))实际上执行了3个动作:首先调用enable_shared_from_this<A>的构造函数;其次调用A的构造函数;最后调用shared_ptr<A>的构造函数。是第3个动作设置了enable_shared_from_this<A>weak_ptr

#include <iostream>#include <memory>using namespace std;class A:public enable_shared_from_this<A>{public:    string id;    A(string id):id(id){        cout<<id<<":构造函数"<<endl;//        shared_ptr<A> pa = shared_from_this(); // 抛出异常    }    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    shared_ptr<A> shared_pa(new A("shared_ptr"));    return 0;}
  • 在类的继承树中不能有2个或更多个enable_shared_from_this<T>

在下面的代码中,子类B并没有直接继承enable_shared_from_this,而是使用dynamic_pointer_cast进行了类型转换。

#include <iostream>#include <memory>using namespace std;class A:public enable_shared_from_this<A>{public:    A(){        cout<<"调用构造函数A!"<<endl;    }    virtual ~A(){        cout<<"调用析构函数A!"<<endl;    }    shared_ptr<A> getA(){        return shared_from_this();    }};class B:public A{public:    B(){        cout<<"调用构造函数B!"<<endl;    }    virtual ~B(){        cout<<"调用析构函数B!"<<endl;    }    shared_ptr<B> getB(){        return dynamic_pointer_cast<B>(shared_from_this());    }};int main() {    shared_ptr<B> shared_pa(new B());    cout<<shared_pa.use_count()<<endl;    shared_ptr<A> shared_pb = shared_pa->getA();    cout<<shared_pa.use_count()<<endl;    shared_ptr<B> shared_pc = shared_pa->getB();    cout<<shared_pa.use_count()<<endl;    return 0;}

运行结果:

调用构造函数A!调用构造函数B!123调用析构函数B!调用析构函数A

9. 必须判断调用weak_ptr.lock()获取的shared_ptr的有效性

当通过weak_ptr.lock()方法获取shared_ptr时,必须判断该shared_ptr是否有效,因为此时我们期望的shared_ptr指向的对象也许已经被删除了。

#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    weak_ptr<A> weak_pa;    shared_ptr<A> shared_pa(new A("shared_ptr"));    weak_pa = shared_pa;    cout<<weak_pa.lock()<<endl;    shared_pa.reset();    cout<<weak_pa.lock()<<endl;    return 0;}

运行结果:

shared_ptr:构造函数0xfd11c8shared_ptr:析构函数0

10. 尽量使用make函数初始化智能指针

使用make_uniquemake_shared初始化unique_ptrshared_ptr具有如下优点(具体见《智能指针之make_unique与make_shared》):

  • 效率更高

当用new创建一个对象的同时创建一个shared_ptr时,这时会发生两次动态申请内存:一次是给使用new申请的对象本身的,而另一次则是由shared_ptr的构造函数引发的为资源管理对象分配的。

当使用make_shared的时候,C++编译器只会一次性分配一个足够大的内存,用来保存这个资源管理者和这个新建对象。

  • 异常安全

由于C++不保证函数实参求值顺序,若其中一个实参是用new初始化的智能指针右值时,可能会因为异常而产生内存泄漏。

11. 使用shared_ptr指向动态数组时,必须使用自定义deleter

如果没有自定义deletershared_ptr在超出作用域时仅仅会释放指针所指向的对象的内存,即数组的第一个元素,数组的其他元素所在内存未被释放而造成内存泄露。

  • 未自定义deleter情况:
#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    shared_ptr<A> a(new A[2]{A("shared_ptr0"),A("shared_ptr1")});    return 0;}

运行结果:

shared_ptr0:构造函数shared_ptr1:构造函数shared_ptr0:析构函数
  • 自定义deleter情况:
#include <iostream>#include <memory>using namespace std;class A{public:    string id;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    shared_ptr<A> a(new A[2]{A("shared_ptr0"),A("shared_ptr1")},                    [](A *a){                        delete []a;                    });    return 0;}

运行结果:

shared_ptr0:构造函数shared_ptr1:构造函数shared_ptr1:析构函数shared_ptr0:析构函数

12. 使用shared_ptr时应避免循环引用

shared_ptr所指向的对象中包含shared_ptr类型的成员变量时,应格外小心,防止由于循环引用而导致的内存泄漏。

下面代码展示了最简单的循环引用情况,shared_pa所指向的对象的引用计数为2,当离开作用域时,shared_pa本身被从栈上销毁,对象的引用计数减1,仍大于0,该对象未被释放。

所以,在设计类的时候,当不需要对象的所有权,也不想指定这个对象的生命周期时,可以考虑使用weak_ptr代替shared_ptr

  • 未使用weak_ptr情况:
#include <iostream>#include <memory>using namespace std;class A{public:    string id;    shared_ptr<A> ptr;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    shared_ptr<A> shared_pa(new A("shared_ptr"));    shared_pa->ptr = shared_pa;    return 0;}

运行结果:

shared_ptr:构造函数
  • 使用weak_ptr情况:
#include <iostream>#include <memory>using namespace std;class A{public:    string id;    weak_ptr<A> ptr;    A(string id):id(id){cout<<id<<":构造函数"<<endl;}    ~A(){cout<<id<<":析构函数"<<endl;}};int main() {    shared_ptr<A> shared_pa(new A("shared_ptr"));    shared_pa->ptr = shared_pa;    return 0;}

运行结果:

shared_ptr:构造函数shared_ptr:析构函数

13. share_ptr的类型转换不能使用C++常用的转型函数

share_ptr的类型转换不能使用C++常用的转型函数,即static_cast,dynamic_cast,const_cast,而要使用static_pointer_cast,dynamic_pointer_cast,const_pointer_cast

static_cast,dynamic_cast,const_cast的功能是转换成对应的模版类型,即static_cast<T*>其实是转换成类型为T的指针。使用简单的C++转型函数是将share_ptr对象转型为模版指针对象,导致转型的模版指针对象不能采用share_ptr进行管理。因此,share_ptr为了支持转型,提供了类似的转型函数即static_pointer_cast<T>,从而使转型后仍然为shared_pointer对象,仍然可以对指针进行管理。

#include <iostream>#include <memory>using namespace std;class A{public:    int a;    virtual ~A(){}};class AA:public A{public:    int aa;};class B{public:    int b;};int main() {    shared_ptr<AA> spa(new AA());    shared_ptr<A> spb = spa;    shared_ptr<AA> spc = dynamic_pointer_cast<AA>(spb);    shared_ptr<void> spd = spb;    shared_ptr<AA> spe = static_pointer_cast<AA>(spd);    shared_ptr<B> spf = static_pointer_cast<B>(spd);//    shared_ptr<B> spg = static_pointer_cast<B>(spb); // 编译错误    cout<<spa.use_count()<<endl;    return 0;}

运行结果:

6

14. shared_ptr没有保证共享对象的线程安全性

shared_ptr可以让你通过多个指针来共享资源,这些指针自然可以用于多线程。有些人想当然地认为用一个shared_ptr来指向一个对象就一定是线程安全的,这是错误的。你仍然有责任使用一些同步原语来保证被shared_ptr管理的共享对象是线程安全的。

参考链接

使用 C++11 智能指针时要避开的 10 大错误
auto_ptr 代码及缺陷
C++11智能指针之unique_ptr
静态或者全局智能指针使用的注意几点
为什么函数可以返回unique_ptr
What is the usefulness of enable_shared_from_this?
Shared_from_this 几个值得注意的地方
c++ shared_ptr智能指针使用注意事项

原创粉丝点击