介绍 C++ 的智能指针 (Smart Pointers) 相关 API。

C++ 中的智能指针是为了解决内存泄漏、重复释放等问题而提出的,它基于 RAII (Resource Acquisition Is Initialization),也称为“资源获取即初始化” 的思想实现。智能指针实质上是一个类,但经过封装之后,在行为语义上的表现像指针。

参考资料:

shared_ptr

shared_ptr 能够记录多少个 shared_ptr 共同指向一个对象,从而消除显式调用 delete,当引用计数变为零的时候就会将对象自动删除。

注意,这里使用 shared_ptr 能够实现自动 delete ,但是如果使用之前仍需要 new 的话,代码风格就会变得很「奇怪」,因为 new/delete 总是需要成对出现的,所以尽可能使用封装后的 make_shared 来「代替」new

shared_ptr 基于引用计数实现,每一个 shared_ptr 的拷贝均指向相同的内存。如果某个 shared_ptr 被析构(生命周期结束),那么引用计数减 1 ,当引用计数为 0 时,自动释放指向的内存。

shared 的所有成员函数,包括拷贝构造函数 (Copy Constructor) 和拷贝赋值运算 (Copy Assignment Operator),都是线程安全的,即使这些 shared_ptr 指向同一对象。但如果是多线程访问同一个 non-const 的 shared_ptr ,那有可能发生资源竞争 (Data Race) 的情况,比如改变这个 shared_ptr 的指向,因此这种情况需要实现多线程同步机制。当然,可以使用 shared_ptr overloads of atomic functions 来防止 Data Race 的发生。

内部实现

如下图所示,shared_ptr 内部仅包括 2 个指针,一个指针指向共享对象,另外一个指针指向 Control block .

[CPP] 智能指针-LMLPHP

初始化

  1. 通过构造函数初始化()

下面是正确的方式。

void func1()
{
    int *a = new int[10];
    shared_ptr<int[]> p(a);
    // a is same as p.get()
    cout << a << endl;
    cout << p.get() << endl;
    for (int i = 0; i < 10; i++) p[i] = i;
    for (int i = 0; i < 10; i++) cout << a[i] << ' ';
}
// Output: 1-9

下面是错误的方式,因为 ptr 析构时会释放 &a 这个地址,但这个地址在栈上(而不是堆),因此会发生运行时错误。

int main()
{
    int a = 10;
    shared_ptr<int> ptr(&a);
    // a is same as p.get(), but runs fail
    cout << &a << endl;
    cout << ptr.get() << endl;
}
  1. 如果通过 nullptr 初始化,那么引用计数的初始值为 0 而不是 1 。
shared_ptr<void *> p(nullptr);
cout << p.use_count() << endl;
  1. 不允许通过一个原始指针初始化多个 shared_ptr
int main()
{
    int *p = new int[10];
    shared_ptr<int> ptr1(p);
    shared_ptr<int> ptr2(p);
    cout << p << endl;
    cout << ptr1.get() << endl;
    cout << ptr2.get() << endl;
}

)。

作为函数参数或返回值

unique_ptr 作为函数参数,只能通过引用,或者 move 操作实现。

下列操作无法通过编译:

void func5(unique_ptr<Base> ptr) {}
int main()
{
    unique_ptr<Base> ptr(new Base());
    func5(ptr);
}

需要改成:

void func5(unique_ptr<Base> &ptr) {}
func(ptr);

或者通过 move 转换为右值引用:

void func5(unique_ptr<Base> ptr)
{
    cout << "ptr in function: " << ptr.get() << endl;
}
int main()
{
    auto p = new Base();
    cout << "p = " << p << endl;
    unique_ptr<Base> ptr(p);
    func5(move(ptr));
    cout << "ptr in main: " << ptr.get() << endl;
}
/* Output is:
   Base
   p = 0xa66c20
   ptr in function: 0xa66c20
   ~Base
   ptr in main: 0
 */

unique_ptr 作为函数返回值,会自动发生 U = move(V) 的操作(转换为右值引用):

unique_ptr<Base> func6()
{
    auto p = new Base();
    unique_ptr<Base> ptr(p);
    cout << "In function: " << ptr.get() << endl;
    return ptr;
}
int main()
{
    auto ptr = func6();
    cout << "In main: " << ptr.get() << endl;
}

成员函数

例子

#include <vector>
#include <memory>
#include <iostream>
#include <fstream>
#include <functional>
#include <cassert>
#include <cstdio>

using namespace std;

// helper class for runtime polymorphism demo
class B
{
public:
    virtual void bar() { cout << "B::bar\n"; }
    virtual ~B() = default;
};
class D : public B
{
public:
    D() { cout << "D::D\n"; }
    ~D() { cout << "D::~D\n"; }
    void bar() override { cout << "D::bar\n"; }
};

// a function consuming a unique_ptr can take it by value or by rvalue reference
unique_ptr<D> passThrough(unique_ptr<D> p)
{
    p->bar();
    return p;
}
// helper function for the custom deleter demo below
void close_file(FILE *fp) { std::fclose(fp); }

// unique_ptr-base linked list demo
class List
{
public:
    struct Node
    {
        int data;
        unique_ptr<Node> next;
        Node(int val) : data(val), next(nullptr) {}
    };
    List() : head(nullptr) {}
    ~List() { while (head) head = move(head->next); }
    void push(int x)
    {
        auto t = make_unique<Node>(x);
        if (head) t->next = move(head);
        head = move(t);
    }

private:
    unique_ptr<Node> head;
};

int main()
{
    cout << "unique ownership semantics demo\n";
    {
        auto p = make_unique<D>();
        auto q = passThrough(move(p));
        assert(!p), assert(q);
    }

    cout << "Runtime polymorphism demo\n";
    {
        unique_ptr<B> p = make_unique<D>();
        p->bar();
        cout << "----\n";

        vector<unique_ptr<B>> v;
        v.push_back(make_unique<D>());
        v.push_back(move(p));
        v.emplace_back(new D());
        for (auto &p : v) p->bar();
    }

    cout << "Custom deleter demo\n";
    ofstream("demo.txt") << "x";
    {
        unique_ptr<FILE, decltype(&close_file)> fp(fopen("demo.txt", "r"), &close_file);
        if (fp) cout << (char)fgetc(fp.get()) << '\n';
    }

    cout << "Linked list demo\n";
    {
        List list;
        for (long n = 0; n != 1000000; ++n) list.push(n);
        cout << "Pass!\n";
    }
}

weak_ptr

weak_ptr 指针通常不单独使用(因为没有实际用处),只能和 shared_ptr 类型指针搭配使用。

weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数。

此外,weak_ptr 没有重载 *-> 运算符,因此 weak_ptr 只能访问所指的堆内存,而无法修改它。

weak_ptr 作为一个 Observer 的角色存在,可以获取 shared_ptr 的引用计数,可以读取 shared_ptr 指向的对象。

成员函数:

例子:

#include <memory>
#include <iostream>
using namespace std;
// global weak ptr
weak_ptr<int> gw;
void observe()
{
    cout << "use count = " << gw.use_count() << ": ";
    if (auto spt = gw.lock()) cout << *spt << "\n";
    else cout << "gw is expired\n";
}
int main()
{
    {
        auto sp = make_shared<int>(233);
        gw = sp;
        observe();
    }
    observe();
}
// Output:
// use count = 1: 233
// use count = 0: gw is expired

总结

使用智能指针的几个重要原则是:

  • 永远不要试图去动态分配一个智能指针,相反,应该像声明函数的局部变量那样去声明智能指针。
  • 使用 shared_ptr 要注意避免循环引用
01-19 09:09