为什么需要智能指针:

在传统的C++中,程序员一般都使用new和delete来手动管理内存,这就带来了内存泄漏

① 忘记delete:在分配内存后,由于复杂的程序逻辑或者异常而忘记delete,那么这块内存在程序结束前都无法被释放

② 异常:如果程序在new和delete之间抛出了异常,delete语句可能不会执行

其核心思想:就是将一个原生指针封装在一个智能指针对象当中,而当该对象离开作用域时,它的析构函数会自动调用delete来释放其管理的内存。也被称为RAII(资源获取即初始化)资源在对象构造时获取,在对象析构(也就是相当于离开作用域)时自动释放,实现自动管理内存

一、std::unique_ptr(独占所有权智能指针)

在同一时间,一个对象只能由一个unique_ptr所拥有,不能被复制(也就是不允许拷贝构造和拷贝赋值),但是支持移动(移动构造和移动赋值)

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int value) : m_data(value) {
        std::cout << "MyClass constructed with " << m_data << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destroyed with " << m_data << std::endl;
    }
    void print() const { 
        std::cout << "data: " << m_data << std::endl; 
    }
private:
    int m_data;
};

int main() {
    // 创建一个 unique_ptr
    std::unique_ptr<MyClass> p1(new MyClass(10));
    std::unique_ptr<MyClass> p3;
    p3.reset(new MyClass(20));
    std::unique_ptr<MyClass> p4 = std::make_unique<MyClass>(30);
    p1->print(); 
    // 编译错误!无法复制 unique_ptr
    // std::unique_ptr<MyClass> p2 = p1;
    // 但是支持移动
    std::unique_ptr<MyClass> p2 = std::move(p1);

    if (!p1) {
        std::cout << "p1 is now null after move." << std::endl;
    }

    if (p2) {
        p2->print(); // p2 现在拥有对象
    }
    // 当 p2 离开作用域,自动调用析构函数
    return 0;
}
MyClass constructed with 10
MyClass constructed with 20
MyClass constructed with 30
data: 10
p1 is now null after move.
data: 10
MyClass destroyed with 10
MyClass destroyed with 30
MyClass destroyed with 20
二、std::shared_ptr(共享所有权智能指针)

一个对象可以有多个 shared_ptr 指向它。内部使用引用计数机制来跟踪有多少个 shared_ptr 共享同一个对象。当最后一个 shared_ptr 被销毁或重置时,对象才会被销毁。

如果想查看有多少个共享智能指针同时管理这块内存,就可以使用一个成员函数 use_count

如果一个智能指针被初始化了一块有效的内存,那这块内存的引用计数加一,如果没有初始化或者被初始化为nullptr,引用计数不变

初始化:

#include <iostream>
#include <memory>

int main() {
    // 一、通过构造函数初始化
    std::shared_ptr<int> p0;
    std::cout << "p0管理的内存的引用计数为:" << p0.use_count() << std::endl;
    std::shared_ptr<int> p1(new int(9));
    std::cout << "p1管理的内存的引用计数为:" << p1.use_count() << std::endl;
    // 调用拷贝构造
    std::shared_ptr<int> p2(p1);
    std::cout << "p2管理的内存的引用计数为:" << p2.use_count() << std::endl;
    // 调用移动构造
    std::shared_ptr<int> p3(std::move(p1));
    std::cout << "p3管理的内存的引用计数为:" << p3.use_count() << std::endl;
    // 二、通过std::make_shared初始化
    std::shared_ptr<int> p4 = std::make_shared<int>(9);
    std::cout << "p4管理的内存的引用计数为:" << p4.use_count() << std::endl;
    // 三、通过reset初始化
    std::shared_ptr<int> p5;
    p5.reset(new int(10));
    std::cout << "p5管理的内存的引用计数为:" << p5.use_count() << std::endl;
     return 0;   
}

注:对于一个还没有初始化的智能指针,可以用reset初始化,但是当智能指针中有值时,调用reset后,其引用计数会减一

p0管理的内存的引用计数为:0
p1管理的内存的引用计数为:1
p2管理的内存的引用计数为:2
p3管理的内存的引用计数为:2
p4管理的内存的引用计数为:1
p5管理的内存的引用计数为:1
三、std::weak_ptr(弱引用智能指针,与std::shared_ptr配合使用)

weak_ptr可以看作shared_ptr的助手,它不管理shared_ptr内部的指针,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数。其主要作用是监视shared_ptr中管理的资源是否存在

#include <iostream>
#include <memory>
int main() {
    std::shared_ptr<int> p0(new int(9));
    std::weak_ptr<int> w_p1;  // 空的weak_ptr对象
    std::weak_ptr<int> w_p2(w_p1);  // 通过空weak_ptr对象构造另一个空对象
    std::weak_ptr<int> w_p3(p0);  // 通过p0构造了可用的weak_ptr实例对象
    std::weak_ptr<int> w_p4;
    w_p4 = p0;
    std::cout << "p0 --> use_count " << p0.use_count() << std::endl;
    std::cout << "w_p1 --> use_count " << w_p1.use_count() << std::endl;
    std::cout << "w_p2 --> use_count " << w_p2.use_count() << std::endl;
    std::cout << "w_p3 --> use_count " << w_p3.use_count() << std::endl;
    std::cout << "w_p4 ---> use_count " << w_p4.use_count() << std::endl;
    return 0;
}
p0 --> use_count 1
w_p1 --> use_count 0
w_p2 --> use_count 0
w_p3 --> use_count 1
w_p4 ---> use_count 1

通过输出结果可以清楚,weak_ptr对象w_p3和w_p4监测的资源是同一个,但它的计数没有变化,证明了weak_ptr只是监测shared_ptr中资源,并不管理资源

其他常用函数

expired() lock() 前者来判断所监测的资源是否释放,后者则是获取管理所监测资源的shared_ptr对象

#include <iostream>
#include <memory>
int main() {
    std::shared_ptr<int> p0(new int(9));
    std::weak_ptr<int> w_p1(p0);
    // bool expired()
    if(w_p1.expired()) {
        std::cout << "资源已被释放" << std::endl;
    }
    else {
        std::cout << "资源未被释放" << std::endl;
    }
    p0.reset();
    if(w_p1.expired()) {
        std::cout << "资源已被释放" << std::endl;
    }
    else {
        std::cout << "资源未被释放" << std::endl;
    }

    std::shared_ptr<int> p1, p2;
    std::weak_ptr<int> w_p2;
    p1 = std::make_shared<int>(10);
    w_p2 = p1;
    p2 = w_p2.lock();
    std::cout << "use_count " << w_p2.use_count() << std::endl;

    p1.reset();
    std::cout << "use_count " << w_p2.use_count() << std::endl;

    p1 = w_p2.lock();
    std::cout << "use_count " << w_p2.use_count() << std::endl;
    return 0;
}
资源未被释放
资源已被释放
use_count 2
use_count 1
use_count 2
解决循环引用问题
#include <iostream>
#include <memory>
class A;
class B;
class A {
public:
    std::shared_ptr<B> p1;
    ~A() {
        std::cout << "A被销毁" << std::endl;
    }
};
class B {
public:
    std::shared_ptr<A> p2;
    ~B() {
    std::cout << "B被销毁" << std::endl;
    }
};
int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->p1 = b;
    b->p2 = a;
    // 主函数结束,a和b相互持有,导致引用技术无法归零
    // 析构函数不会执行,内存泄漏
    return 0;
}

解决方法

#include <iostream>
#include <memory>
class A;
class B;
class A {
public:
    std::shared_ptr<B> p1;
    ~A() {
        std::cout << "A被销毁" << std::endl;
    }
};
class B {
public:
    std::weak_ptr<A> w_p2;
    ~B() {
    std::cout << "B被销毁" << std::endl;
    }
};
int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->p1 = b;
    b->w_p2 = a;
    return 0;
}

主函数结束时,a释放,其引用计数减一,为0,A销毁

A销毁后,其持有的p1释放,b引用计数减一

b释放,b引用计数再减一,变为0,B被销毁

A被销毁
B被销毁
小结

特性

std::unique_ptr

std::shared_ptr

std::weak_ptr

所有权

独占

共享

无所有权(弱引用)

复制语义

禁止复制,允许移动

允许复制和移动

允许复制和移动

使用场景

明确独占资源的场景

需要共享所有权的场景

解决 shared_ptr

循环引用

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐