shared_ptr

shared_ptr允许多个该智能指针共享的 “拥有” 同一堆分配对象的内存, 这通过引用计数(reference counting) 实现, 会记录有多少个shared_ptr共同指向一个对象, 一旦最后一个这样的指针被销毁, 也就是一旦某个对象的引用计数变为0, 这个对象会被自动删除.

void mytest()
{
    std::shared_ptr<int> sp1(new int(22));
    std::shared_ptr<int> sp2 = sp1;
    std::cout << "cout: " << sp2.use_count() << std::endl; // 打印引用计数

    std::cout << *sp1 << std::endl;
    std::cout << *sp2 << std::endl;

    sp1.reset(); // 显示让引用计数减一
    std::cout << "count: " << sp2.use_count() << std::endl; // 打印引用计数

    std::cout << *sp2 << std::endl; // 22

    return;
}

多个shared_ptr指向同一个对象时候, 这些shared_ptr指针指向的数据的地址是相同的, 并且这些shared_ptr实例其实都是同一个, 即一个单例. 只是这个实例中保存了引用计数个数. 可以通过获取shared_ptr自身地址可知只有一个shared_ptr实例对象.

weak_ptr

weak_ptr是为配合shared_ptr而引入的一种智能指针, 来协助shared_ptr工作, 它可以从一个shared_ptr或另一个weak_ptr对象构造, 它的构造和析构不会引起引用计数的增加或减少. 没有重载 * 和 -> 但可以使用lock()接口获得一个可用的shared_ptr对象.

weak_ptr的使用更为复杂一点, 它可以指向shared_ptr指针指向的对象内存, 却并不拥有该内存, 而使用weak_ptr成员lock(), 则可返回其指向内存的一个share_ptr对象, 且在所指对象内存已经无效时, 返回指针空值nullptr.

注意:weak_ptr并不拥有资源的所有权, 所以不能直接使用资源. 可以从一个weak_ptr构造一个shared_ptr以取得共享资源的所有权.

void check(std::weak_ptr<int> &wp)
{
    std::shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
    if (sp != nullptr)
    {
        std::cout << "still: " << *sp << std::endl;
    } 
    else
    {
        std::cout << "still: " << "pointer is invalid" << std::endl;
    }
}


void mytest()
{
    std::shared_ptr<int> sp1(new int(22));
    std::shared_ptr<int> sp2 = sp1;
    std::weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象

    std::cout << "count: " << wp.use_count() << std::endl; // count: 2
    std::cout << *sp1 << std::endl; // 22
    std::cout << *sp2 << std::endl; // 22
    check(wp); // still: 22
    
    sp1.reset();
    std::cout << "count: " << wp.use_count() << std::endl; // count: 1
    std::cout << *sp2 << std::endl; // 22
    check(wp); // still: 22

    sp2.reset();
    std::cout << "count: " << wp.use_count() << std::endl; // count: 0
    check(wp); // still: pointer is invalid

    return;
}

wark_ptr的深入研究

我们知道shared_ptr是采用引用计数的智能指针, 多个shared_ptr实例可以指向同一个动态对象, 并维护了一个共享的引用计数器. 对于引用计数法实现的计数, 总是避免不了循环引用(或环形引用)的问题, shared_ptr也不例外.

#include <memory>
using namespace std;

class Class_B;

class Class_A {
public:
    Class_A()  { cout << "construct Class_A..." << endl; }
    ~Class_A() { cout << "destruct Class_A..." << endl; }
    shared_ptr<Class_B> pb_;  //在A中引用B
};

class Class_B {
public:
    Class_B()  { cout << "construct Class_B..." << endl; }
    ~Class_B() { cout << "destruct Class_B..." << endl; }
    shared_ptr<Class_A> pa_;  //在B中引用A
};

int main() {
    shared_ptr<Class_A> spa = make_shared<Class_A>();
    shared_ptr<Class_B> spb = make_shared<Class_B>();
    spa->pb_ = spb;
    spb->pa_ = spa;
}
//////////////////// 输出以下:
construct Class_A...
construct Class_B...

从上面代码中, ClassA和ClassB间存在着循环引用, 从运行结果中我们可以看到:当main函数运行结束后, spa和spb管理的动态资源并没有得到释放, 产生了内存泄露.

weak_ptr是为了配合shared_ptr而引入的一种智能指针, 它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期, 也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数. 不论是否有weak_ptr指向, 一旦最后一个指向对象的shared_ptr被销毁, 对象就会被释放. 从这个角度看, weak_ptr更像是shared_ptr的一个助手而不是智能指针.

weak_ptr创建

当我们创建一个weak_ptr时, 需要用一个shared_ptr实例来初始化weak_ptr, 由于是弱共享, weak_ptr的创建并不会影响shared_ptr的引用计数值.

int main() {
    shared_ptr<int> sp(new int(5)); //此时 sp.use_count()==1
    weak_ptr<int> wp(sp);  //此时 sp.use_count()==1
}

判断weak_ptr指向对象是否存在

既然weak_ptr并不改变其所共享的shared_ptr实例的引用计数, 那就可能存在weak_ptr指向的对象被释放掉这种情况. 这时, 我们就不能使用weak_ptr直接访问对象. 那么我们如何判断weak_ptr指向对象是否存在呢?C++中提供了lock函数来实现该功能. 如果对象存在, lock()函数返回一个指向共享对象的shared_ptr, 否则返回一个空shared_ptr.

class Class_A {
public:
    Class_A()  { cout << "construct Class_A..." << endl; }
    ~Class_A() { cout << "destruct Class_A..." << endl; }
    int a;
};

int main() {
    shared_ptr<Class_A> sp(new Class_A());
    weak_ptr<Class_A> wp(sp);

    //sp.reset();
    if (shared_ptr<Class_A> pa = wp.lock()) {
        cout << "get, num:" << pa->a <<endl;
    }else {
        cout << "wp point to nil" <<endl;
    }
}

试试把sp.reset()这行的注释去掉看看结果有什么不同. 除此之外, weak_ptr还提供了expired()函数来判断所指对象是否已经被销毁.

class A
{
public:
    A() : a(3) { cout << "A Constructor..." << endl; }
    ~A() { cout << "A Destructor..." << endl; }

    int a;
};

int main() {
    shared_ptr<A> sp(new A());
    weak_ptr<A> wp(sp);
    sp.reset(); // 此时sp被销毁
    cout << wp.expired() << endl;  // true表示已被销毁, 否则为false
}

/////////////// 输出:
A Constructor...
A Destructor...
1

weak_ptr并没有重载operator->和operator * 操作符, 因此不可直接通过 weak_ptr使用对象, 典型的用法是调用其lock函数来获得shared_ptr示例, 进而访问原始对象. 最后, 我们来看看如何使用weak_ptr来改造最前面的代码, 打破循环引用问题.

class ClassB;

class ClassA
{
public:
    ClassA() { cout << "ClassA Constructor..." << endl; }
    ~ClassA() { cout << "ClassA Destructor..." << endl; }
    weak_ptr<ClassB> pb;  // 在A中引用B
};

class ClassB
{
public:
    ClassB() { cout << "ClassB Constructor..." << endl; }
    ~ClassB() { cout << "ClassB Destructor..." << endl; }
    weak_ptr<ClassA> pa;  // 在B中引用A
};

int main() {
    shared_ptr<ClassA> spa = make_shared<ClassA>();
    shared_ptr<ClassB> spb = make_shared<ClassB>();
    spa->pb = spb;
    spb->pa = spa;
    // 函数结束, 思考一下:spa和spb会释放资源么?
}

//////////////输出:
ClassA Constructor...
ClassB Constructor...
ClassA Destructor...
ClassB Destructor...
Program ended with exit code: 0

unique_ptr

unique_ptr持有对象的独有权, 同一时刻只能有一个unique_ptr指向给定对象 (通过禁止拷贝语义、只有移动语义来实现). unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始, 直到离开作用域.

离开作用域时, 若其指向对象, 则将其所指对象销毁(默认使用delete操作符, 用户可指定其他操作).

void mytest()
{
    std::unique_ptr<int> up1(new int(11));   // 无法复制的unique_ptr
    //unique_ptr<int> up2 = up1;        // err, 不能通过编译
    std::cout << *up1 << std::endl;   // 11

    std::unique_ptr<int> up3 = std::move(up1);    // 现在p3是数据的唯一的unique_ptr

    std::cout << *up3 << std::endl;   // 11
    //std::cout << *up1 << std::endl;   // err, 运行时错误
    up3.reset();            // 显式释放内存
    up1.reset();            // 不会导致运行时错误
    //std::cout << *up3 << std::endl;   // err, 运行时错误

    std::unique_ptr<int> up4(new int(22));   // 无法复制的unique_ptr
    up4.reset(new int(44)); //"绑定"动态对象
    std::cout << *up4 << std::endl; // 44

    up4 = nullptr;//显式销毁所指对象, 同时智能指针变为空指针. 与up4.reset()等价

    std::unique_ptr<int> up5(new int(55));
    int *p = up5.release(); //只是释放控制权, 不会释放内存
    std::cout << *p << std::endl;
    //cout << *up5 << endl; // err, 运行时错误
    delete p; //释放堆区资源

    return;
}

unique_ptr.release(): 只是释放控制权, 不会释放内存 unique_ptr.reset(): 重新绑定动态对象

使用unique_ptr + raii自动释放对象


struct D {
    void operation() (Foo* fo) {
        std::cout << "handle something && D operator() " << std::endl;
        delete fo;
    }
}


void TestA() {

    {
        std::unique_ptr<Foo, D> p1(new Foo);

        ...
    } // 当 p1 生命期结束时候自动调用自定义deleter, 执行析构
}