C++11新特性之智能指针

 

动态内存与智能指针

动态内存

在C++中,动态内存的管理是通过一对运算符来完成的: new, 在动态内存中为对象分配空间并返回一个指向该对象的指针,也可以选择对对象进行初始化 delete, 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存

智能指针

为了更容易,也更安全的使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。智能指针的行为类似于常规指针,区别在于它负责自动释放所指向的对象。

含义

  • shared_ptr 允许多个指针指向同一个对象
  • unique_ptr 只能”独占”所指向的对象

初始化

智能指针也是一个模板。所以,创建智能指针时,必须提供指针类型,与vector一样,在尖括号内给出类型。

shared_ptr<std::string> p1;     //p1指向string
shared_ptr<std::list<int>> p2;  //p2指向list

智能指针默认初始化是一个空指针。

使用方式

//如果p1不为空,检查它是否指向一个空string。
if(p1 && p1->empty())
    *p1 = "hi";     //如果p1指向一个空string,解引用p1,将一个新值赋予string

共同操作

操作函数 操作含义 是否支持
shared_ptr<T> p 空智能指针,可以指向类型为T的对象 都支持
unique_ptr<T>    
p 将p用作一个条件判断,若p指向一个对象,则为true  
*p 解引用p, 获得它指向的对象  
p->mem 等价于与(*p).mem  
p.get() 返回p中保存的指针(慎用)  
swap(p, q) 交换p 和 q 中指针  
p.swap(q)    

make_shared函数

它是一个标准库函数,定义在头文件memory中,在动态内存中分配一个对象并初始化它,返回指向该对象的shared_ptr。如下:

shared_ptr<int> p3 = make_shared<int>(43);  //指向一个值为42的int的shared_ptr
shared_ptr<string> p4 = make_shared<string>(10, '9');   //p4指向一个值为“9999999999”的string
shared_ptr<int> p5 = make_shared<int>();    //p5指向一个值初始化的int,即值为0

调用make_shared时传递的参数就是给类型T的构造函数参数,如为空,对象就会进行值初始化。

shared_ptr

当拷贝或赋值操作时,每个shared_ptr都会记录总共有多个shared_ptr指向这个相同的对象。如下:

shared_ptr<int> p = make_shared<int>(43);   //p指向的对象只有p一个引用者
shared_ptr<int> q(p);   //p和q指向相同对象,此对象有两个引用者

shared_ptr操作

| 操作函数 | 操作含义 | 是否支持 | | :—— | :——: | :——: | |make_shared<T>(args) | 返回一个shared_ptr,指向一个动态分配的类型为T的对象,并用args初始化该对象 | shared_ptr独有 | | shared_ptr<T> p(q) | p是shared_ptr的拷贝,此操作会递增q中的计数器。q中的指针必须能转换为T* | | p = q | p 和 q 都是shared_ptr,所保存的指针必须能相互转换。| | p.use_count() | 返回与p共享对象的智能指针数量;耗时,尽量用于调试 | | p.unique() | 若p.use_count()为1,返回true;否则返回false | 每个shared_ptr内部都有一个关联的计数器,通常称为引用计数(reference count)

计数器增加。如下:

  • 用shared_ptr初始化另一个shared_ptr
  • 作为参数传递给一个函数
  • 作为函数的返回值

计数器递减。如下:

  • 给shared_ptr赋予新值
  • shared_ptr被销毁
  • 局部的shared_ptr离开其作用域

当shared_ptr的计数器变为0时,它就会自动释放自己所管理的对象,调用自己的析构函数(destructor)来释放资源,完成销毁工作。

shared_ptr<int> r = make_shared<int>(43);   //p指向的对象只有r一个引用者
r = q;  //给r赋值,令它向另一个地址
        //递增q指向的对象的引用计数
        //递减r原来指向的对象的引用计数
        //r原来指向的对象已没有引用者,会自动释放
class StrBlob
{
public:
    using size_type = std::vector<std::string>::size_type;

    StrBlob(): data(std::make_shared<std::vector<std::string>>()) {}
    StrBlob(std::initializer_list<std::string> il):
            data(std::make_shared<std::vector<std::string>>(il)){}
    ~StrBlob() {}

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    void push_back(const std::string &t) { data->push_back(t); }
    void pop_back()
    {
        check(0, "pop_back on empty StrBlob");
        return data->pop_back();
    }
    const std::string& front()
    {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    const std::string& back()
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
private:
    void check(size_type i, const std::string &msg) const
    {
        if(i >= data->size())
            out_of_range(msg);
    }
private:
    std::shared_ptr<std::vector<std::string>> data;
};

unique_ptr

一个unique_ptr只能拥有一个对象,所以它不支持普通的拷贝和赋值操作,更改指向对象或者销毁unique_ptr时,指向的对象都会被销毁。初始化时,没有类似make_shared这样的标准库函数返回一个unique_ptr,所以必须采用直接初始化形式。

unique_ptr<double> p1;
unique_ptr<int> p2();
unique_ptr<int> p3(new int(42));
//unique_ptr<int> p4(p2);   //错误:不支持拷贝
//unique_ptr<int> p5; 
// p5 = p2;                 //错误:不支持赋值

unique_ptr操作

操作函数 操作含义 是否支持
unique_ptr<T> u1 空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放指针 unique_ptr独有
unique_ptr<T, D> u2 如上,但是u2会使用一个类型为D的可调用对象来释放它的指针  
unique_ptr<T, D> u(d) 空unique_ptr,用类型为D的对象d来代替delete  
u = nullptr 释放u指向的对象,将u置空  
u.release() u放弃对指针的控制权, 返回指针,并将u置空 (如用指针变量接收这个返回值,切记要delete这个指针)  
u.reset() 释放u指向的对象  
u.reset(q) 如果提供了内置指针q,令u指向这个对象;否则将u置空  
u.reset(nullptr) 释放并置空  

拷贝和赋值

unique_ptr<string> p2(p1.release());    //将p1指向的对象转移给p2,并将p1置空
unique_ptr<string> p3(new string("test"));
p2.reset(p3.release()); //将所有权从p3转移给p2, reset释放p2原来指向的对象

参数和返回值

不能拷贝的规则有一个例外:可以拷贝或赋值一个将要被销毁的unique_ptr。如下:

unique_ptr<int> clone(int p)
{
    return unique_ptr<int>(new int(p));
}

unique_ptr<int> clone2(int p)
{
    unique_ptr<int> ret(new int(p));
    return ret;
}

删除器

shared_ptr和unique_ptr默认情况下都用delete释放它指向的对象。我们可以定义一个函数来代替delete,这个__删除器__函数必须能够完成对智能指针中保存的指针进行释放的操作,函数一般是单个参数。 shared_ptr和unique_ptr使用删除器的方式略有不同,shared_ptr不需要再定义是传入函数类型。

class Deleter
{
public:
    void operator() (int* p)
    {
        cout << "delete p, *p=" << *p << endl;
        delete p;
        p = nullptr;
    }
};

auto FuncDeleter = [](int *p) 
{ 
    cout << "delete p, *p=" << *p << endl;
    delete p; 
    p = nullptr; 
    

};
typedef void(*delType)(int *);

void test()
{
    std::shared_ptr<int> s1(new int(1), FuncDeleter);
    Deleter del;
    std::shared_ptr<int> s2(new int(3), del);

    std::unique_ptr<int, decltype(FuncDeleter)> u1(new int(4), FuncDeleter);
    std::unique_ptr<int, delType> u2(new int(5), FuncDeleter);
    std::unique_ptr<int, Deleter> u3(new int(6));
    std::unique_ptr<int, Deleter> u4(new int(7), del);
}

weak_ptr

weak_ptr是一种不控制所指向对象生存期的指针指针,它指向一个有shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数,而shared_ptr对象的引用计数变为0时,该对象会被销毁。即使weak_ptr指向它,对象还是被释放。weak_ptr就是智能指针的一种‘弱’共享对象。

操作方式

方式 含义
weak_ptr<T> w 空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp) 与shared_ptr sp指向相同对象的weak_ptr,T必须能转换为sp指向的类型
w = p p可以时一个shared_ptr或一个weak_ptr, 赋值后w和p共享对象
w.reset() 将w置为空
w.use_count() 与w共享对象的shared_ptr的数量
w.expired() 若w.use_count() 为0,返回true,否则返回false
w.lock() 如果expired为true,返回一个空shared_ptr,否则返回一个指向w的对象的shared_ptr

动态数组