阅读 223

C++智能指针

C++智能指针

指针在c++中非常重要,但使用它也带来了许多问题,即让指针与其所指对象拥有相同的生命周期,特别是当有多个指针指向同一个对象时,这是十分困难的.当有多个指针指向同一个对象时,当其中一个指针销毁时,不让能其它指针出现空悬指针的现象,也不能让该对象被销毁多次;当最后一个指针被销毁时,必须同时销毁该对象,不能造成资源泄漏.

避免上述问题的通常做法是使用智能指针(smart pointer),当它是指象对象的最后一个指针时,在它销毁时,也会将它指向的对象销毁.C++11起标准库提供了两个类型的智能指针:

  1. shared_ptr 共享式拥有的智能指针.该类指针允许多个指针指向同一个对象,该对象会在最后一个指针被销毁时同时被销毁释放.
  2. unique_ptr 独占式拥有的智能指针.该类指针保证在同一时间内只有一个智能指针指向了对象,可以移交拥有权,对于避免资源泄漏特别有用.

1.shared_ptr

多个shared_ptr可以共享同一个对象,当对象的最后一个拥有者销毁时,它有责任销毁对象并清理与该对象相关的所有资源.shared_ptr的目标就是在其所指向的对象不再被需要之后自动释放与对象相关的资源.

shared_ptr的使用

shared_ptr可以像使用其它指针一样使用它,可以进行赋值,拷贝,比较等操作.shared_ptr定义在memory头文件中.

#include <iostream>
#include <string>
#include <vector>
#include <memory>

using namespace std;

int main()
{
    shared_ptr<string> pNico(new string("nico"));
    shared_ptr<string> pJutta(new string("jutta"));     //定义了两个shared指针
    //也可以使用如下方式初始化定义
    //shared_ptr<string> pNico = make_shared<string>("nico");

    (*pNico)[0] = 'N';
    pJutta->replace(0, 1, "J");

    vector<shared_ptr<string>> whoMadeCoffee;
    whoMadeCoffee.push_back(pJutta);
    whoMadeCoffee.push_back(pJutta);
    whoMadeCoffee.push_back(pNico);
    whoMadeCoffee.push_back(pJutta);
    whoMadeCoffee.push_back(pNico);

    for (auto ptr : whoMadeCoffee)
    {
        cout << *ptr << " ";
    }
    cout << endl;

    *pNico = "Nicolai";

    for (auto ptr:whoMadeCoffee)
    {
        cout << *ptr << " ";
    }
    cout << endl;

    cout << "use_count:" << whoMadeCoffee[0].use_count() << endl;
    //use_count()方法会统计当前对象有多少个shared_ptr指针

    system("pause");

    return 0;
}
复制代码

在定义并初始化shared_ptr指针时,建议使用make_shared的方式,因为这种方式比较安全,它使用的是一次分配.而另外一种方式是先定义一个shared_ptr指针,然后再给它赋值一个新的指针,但此时必须用reset()方法.

shared_ptr<string> pNico;
pNico = new string("nico");     //这是不允许的
pNico.reset(new string("nico"));
复制代码

自定义deleter

在声明初始化shared_ptr指针时,也可以为其自定义一个delete函数,如下:

shared_ptr<string> pNico(new string("nico"), [](string *p) {
    cout << "delete " << *p << endl;
    delete p;
});
复制代码

在默认情况下,shared_ptr调用的默认delete函数为delete,而不是delete[],这意味着当shared_ptr在释放对象时,只对单一对象是有效的,而对数组是无效的,此时必须使用自定义delete函数, 或使用为unique_ptr而提供的辅助函数作为deleter。

此外,当某个对象的最后一个shared_ptr不再指象该对象,对此对象进行析构时,当此析构过程中不仅仅是释放内存时,此时必须指定自己的析构策略,即定义自定义delete函数。

2.weak_ptr

weak_ptr指针允许** 共享对象却不拥有 **该对象,当对象的最后一个shared_ptr不再指向该对象时,任何weak_ptr指针就会自动成空。因而,weak_ptr在默认构造函数和拷贝构造函数之外,只提供了接受一个shared_ptr的构造函数。对于weak_ptr指向的对象,不能使用*和->操作函,必须另外建立一个shared_ptr来指向该对象。这是合理的设计,其理由如下:

  1. 在weak_ptr指针之外建立一个shared_ptr指针可因此检查是否存在一个相应对象。如果不,则操作会抛出异常或建立一个空的shared_ptr指针(实际究竟哪种行为乃取决于所执行的操作)。
  2. 当指向的对象正被处理时,shared_ptr指针无法被释放。

因此weak_ptr指针只提供了小量操作,只够用来创建、复制、赋值weak_ptr指针以及转换为一个shared_ptr指针,或检查是否指向某个对象。

weak_ptr的使用

以下例子通过使用weak_ptr指针来避免因使用shared_ptr而引起的环向引用,使得所有shared_ptr指针无法正常释放。

class Person
{
public:
    string name;
    shared_ptr<Person> mother;
    shared_ptr<Person> father;
    vector<weak_ptr<Person>> kids;
    Person(const string &n, shared_ptr<Person> m = nullptr,
        shared_ptr<Person> f = nullptr) :
        name(n), mother(m), father(f)
    {};
    ~Person() {
        cout << "delete " << name << endl;
    };

private:

};

shared_ptr<Person> initFamily(const string& name) {
    shared_ptr<Person> mom(new Person(name + "'s mom"));
    shared_ptr<Person> dad(new Person(name + "'s dad"));
    shared_ptr<Person> kid(new Person(name, mom, dad));
    mom->kids.push_back(kid);
    dad->kids.push_back(kid);
    return kid;
}


int main()
{
    shared_ptr<Person> p = initFamily("nico");
    cout << "nico's family exists" << endl;
    cout << "- nico is shared " << p.use_count() << " times" << endl;
    cout << "- name of 1st kid of nico's mom: " << p->mother->kids[0].lock()->name << endl;

    p = initFamily("jim");
    cout << "jim's family exists" << endl;

    system("pause");
    return 0;
}
复制代码

在使用weak_ptr指针访问被指对象时,必须在weak_ptr指针的式子上加上lock()方法,这会产生一个新的指向该对象的shared_ptr指针。而当weak_ptr指向的对象已经释放时,lock()操作也产生一个空的shared_ptr指针,从而在进行*或->操作时会引发不明确的行为。对于weak_ptr指向的对象是否还继续存在的判断方式有如下几种:

  1. 调用expired()方法,该方法会在weak_ptr指向的对象不存在时返回true,该操作等同于检查use_count()是否为0,但其速度更快。

  2. 使用shared_ptr构造函数显示地将weak_ptr对象转换为shared_ptr对象,如果weak_ptr指向的对象不存在,则构造函数会抛出一个bad_weak_ptr异常。

  3. 调用use_count()方法询问相应对象的拥有者数量,返回0表示不存在任何有效的对象,但该方法的效率不高。

     try
     {
         shared_ptr<string> sp(new string("hi"));
         weak_ptr<string> wp = sp;
         sp.reset();
         cout << wp.use_count() << endl;
         cout << boolalpha << wp.expired() << endl;
         shared_ptr<string> p(wp);
     }
     catch (const exception& e)
     {
         cerr << "exception: " << e.what() << endl;
     }
    复制代码

shared point误用

shared_ptr虽然强化了程序安全,但由于对象的相应资源往往是自动释放的,当对象不再使用时有可能出现问题。

  1. shared_ptr指针的循环依赖,这将会造成空荡指针,即循环依赖中的指针指向的资源无法得到正确的释放。
  2. 必须要确保对象只被一组shared_ptr拥有。当一个对象被多组shared_ptr所拥有时,便有多组shared_ptr对象有权释放相应的资源,相应的资源就会被重复释放而产生问题。要避免这种情况,在创建对象和其相应的资源时直接创建相应的智能指针。

unique_ptr

unique_ptr是一种在异常发生时可帮助避免资源泄漏的智能指针。一般而言,unique_ptr实现了** 独占式 **拥有的概念,即它可以确保一个对象和其所拥有的资源同一时间只被一个指针所拥有。一旦拥有者被销毁或变成空,或开始拥有一个新的对象,先前所拥有的对象就会被销毁,其任何相应资源也会被释放。

unique_ptr使用方式与寻常的指针非常相似,用*来指向对象,用->来方问对象的成员,但它不提供指针的算术运算(如++操作等)。此外,unique_ptr不允许将一个寻常的指针作为初始值,必须直接初始化unique_ptr指针,如下所示:

std::unique_ptr<int> up = new int;      //错误,不能使用寻常指针来初始化
std::unique_ptr<int> up(new int);       //正确
复制代码

unique_ptr指针可以是空指针,可以不指向任何对象,可以让其指向nullptr或调用reset()方法来让其成为空指针。当调用unique_ptr指针的release()方法时,可以获得unique_ptr指针所拥有的对象并放弃拥有权,如下所示:

std::unique_ptr<std::string> up(new std::string("nico"));
std::string* sp = up.release();
复制代码

由于unique_ptr所提供的语义是* 独占式拥有 *,所以不可以对unique_ptr执行寻常意义的拷贝和赋值操作,但可以使用move语义。

unique_ptr的用途

  1. unique_ptr拥有权的转移指出了它的一种用途:函数可以利用它们将拥有权转移给其它函数,即可以将unique_ptr作为函数的入参或返回值,而不用担心资源的泄漏。

  2. unique_ptr可以做为类的成员而避免资源的泄漏。因为只有在一切构造动作全部完成后,析构函数才有可以被调用。而在构造函数执行过程中发生异常时,析构函数是不会被调用的,在构造函数中已分配的资源将不会被回收而造成资源泄漏,用unique_ptr来代替普通的指针可以避免这种情况的发生。而且在使用unique_ptr代替普通的指针后,还可以略而不写析构函数,使用默认析构函数即可。

  3. 数组处理。c++标准库为unique_ptr提供了一个偏特化版本来处理数组,使unique_ptr在遗失其所指向对象的拥有权时,会对该对象调用delete[],其声明方式如下:

     std::unique_ptr<std::string[]> up(new string[10]);
     std::cout<< up[0] <<std::endl;
    复制代码

在这个版本中,unique_ptr指针不再提供*和->操作,改为提供[]下标操作符来访问数组中的某一个对象。

[参考](https://blog.csdn.net/zy19940906/article/details/50470087) C++11中智能指针的原理、使用、实现

文章分类
后端