智能指针

unique_ptr

为什么引入智能指针unique_ptr

普通指针的不足
  1. 内存泄漏:最常见的00问题是忘记释放分配给指针的内存,导致内存泄漏。这通常发生在异常情况或程序员忘记在不再需要内存时释放它。
  2. 悬空指针:当对象被删除,但指针未被重置时,指针就悬空了。悬空指针可能会指向无效的内存区域,尝试访问可能会导致未定义的行为。
  3. 所有权不明:当指针被传递给另一个函数或线程时,很难跟踪谁拥有该指针,并负责最终的删除。这种所有权不明确常导致多重释放或非预期的内存泄漏。
  4. 资源管理困难:对于非内存资源(如文件句柄、网络连接等),使用普通指针进行管理同样需要程序员手动关闭或释放资源,这增加了出错的可能性。
普通指针的释放
  1. 类内的指针:在析构函数中释放。
  2. C++内置数据类型,如何释放?
  3. new出来的类,本身如何释放?
智能指针的设计思路

智能指针的核心设计思路是利用对象的生命周期来管理资源(特别是内存),从而避免资源泄露和其他相关问题。

  1. 智能指针是类模板,在栈上创建智能指针对象
    智能指针在本质上是模板类,意味着它们可以用于指向任何类型的对象。
  2. 把普通指针交给智能指针管理
    当你创建了一个原始指针后,你可以将其交给一个智能指针来管理。这样做的好处是,智能指针会负责这个原始指针所指向的内存的释放工作。
  3. 智能指针对象过期时,调用析构函数释放普通指针的内存
    由于智能指针是在栈上分配的,因此当智能指针的生命周期结束(例如,当它超出作用域时)时,它的析构函数会自动被调用。在智能指针的析构函数中,它会释放其管理的原始指针的内存。

unique_ptr 使用

unique_ptr提供了严格的所有权管理机制,通过独占的方式管理其指向的对象。也就是说unique_ptr拥有其指向的对象,确保同一时间只有一个unique_ptr拥有该对象。当这个unique_ptr被销毁(例如,超出作用域)时,指向的对象也随即被销毁。

使用unique_ptr需要包含头文件#include

unique_ptr的结构

#include 

template <typename T, typename D = std::default_delete<T>>
class unique_ptr {
public:
    // 基础构造函数,接受一个指向已分配对象的指针。
    explicit unique_ptr(T* p = nullptr) noexcept : ptr(p) {}

    // 析构函数,负责释放 unique_ptr 拥有的对象。
    ~unique_ptr() {
        if (ptr) {
            D deleter;
            deleter(ptr); // 默认使用 delete 删除器
        }
    }

    // 移动构造函数,接受一个右值引用至其他 unique_ptr。
    // “窃取”资源,将源 unique_ptr 置为空状态。
    unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }

    // 移动赋值操作符,接受一个右值引用至其他 unique_ptr。
    // 释放当前对象,接管新对象的资源,将源 unique_ptr 置为空状态。
    unique_ptr& operator=(unique_ptr&& other) noexcept {
        if (this != &other) {
            reset(); // 释放当前指针
            ptr = other.ptr; // 接管资源
            other.ptr = nullptr; // 将源 unique_ptr 置空
        }
        return *this;
    }

    // 禁用拷贝构造函数
    unique_ptr(const unique_ptr&) = delete;

    // 禁用拷贝赋值操作符
    unique_ptr& operator=(const unique_ptr&) = delete;

    // 解引用操作符,返回所指对象的引用。
    T& operator*() const {
        return *ptr;
    }

    // 箭头操作符,允许通过 unique_ptr 访问其所拥有对象的成员。
    T* operator->() const noexcept {
        return ptr;
    }

    // 释放 unique_ptr 对象的所有权,返回原始指针,unique_ptr 不再负责对象的删除。
    T* release() noexcept {
        T* old_ptr = ptr;
        ptr = nullptr;
        return old_ptr;
    }

    // 释放 unique_ptr 当前拥有的对象,并可选地接受一个新的对象指针。
    void reset(T* p = nullptr) noexcept {
        if (ptr) {
            D deleter;
            deleter(ptr); // 删除当前对象
        }
        ptr = p; // 接受新的对象指针(如果提供的话)
    }

private:
    T* ptr; // 内部原始指针,指向 unique_ptr 所拥有的对象。
};
  • 第一个模板参数 T 是指针指向的对象类型。
  • 第二个模板参数 D 是用于删除所拥有对象的策略,即删除器。默认情况下,使用的是 default_delete,它会用 delete 操作符来删除对象。

基本用法

测试类AA的定义:

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
	~AA() { cout << m_name << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
1. 初始化

方法一:直接初始化

unique_ptr<AA> p1(new AA("天梦"));

方法二:使用 std::make_unique(C++14 及以后版本推荐使用)

auto p1 = make_unique<AA>("天梦");   // 使用 make_unique (类型推断)
unique_ptr<AA> p2 = make_unique<AA>("天梦");  // 明确指定类型

方法三:使用已存在的指针(不推荐)

AA* p = new AA("西施");
unique_ptr<AA> p0(p);  // 用已存在的地址初始化,这种方式不推荐因为它可能导致所有权不明确。
2. 使用方法

unique_ptr 重载了解引用操作符 * 和箭头操作符 ->,这意味着它们可以像普通指针一样使用,但它们提供了额外的内存管理功能。

auto p = make_unique<AA>("天梦");
p->method();  // 调用对象的方法
(*p).method();  // 同上

不支持拷贝构造和拷贝赋值,因为 unique_ptr 的设计目的是提供对其所指对象的唯一所有权。

下面具体解释一下为什么unique_ptr不支持拷贝构造和拷贝赋值。

我认为unique_ptr它核心是提供了对动态分配内存的“独占所有权”。这种所有权模型具有严格限制,为了避免资源泄露和与其他资源共享相关的问题。

  1. 防止资源的多重删除:如果说unique_ptr能够被复制,那么你最终会得到多个智能指针实例指向同一个资源。这本身并不坏,但问题在于每个实例可能都认为自己“拥有”资源,因此,当这些实例中其中一个离开作用域销毁其拥有的资源后,会导致其他指向同一资源的unique_ptr实例成为野指针,他们也可能尝试删除已经不复存在的资源,从而导致未定义行为,通常是程序崩溃。
  2. 强化唯一所有权语义unique_ptr 的核心思想是它是资源的唯一所有者。允许复制会破坏这种唯一性,因为复制的结果是有两个所有者指向同一个资源。通过禁止复制,unique_ptr 确保了所有权的概念始终得到维护,你可以明确地知道资源的生命周期和所有者身份。
  3. 异常安全:使用原始指针的代码通常对异常情况的处理不够,可能会导致资源泄漏。例如,如果在执行复制操作的过程中抛出异常,程序可能会处于一种状态,其中某些已分配资源没有被清理。而 unique_ptr 通过确保资源只有一个所有者来避免这种情况,即使在异常情况下,也会在 unique_ptr 对象销毁时释放资源。
AA* p = new AA("天梦");
	unique_ptr<AA> pu2 = p;              // 错误,不能把普通指针直接赋给智能指针。
	unique_ptr<AA> pu3 = new AA("天梦"); // 错误,不能把普通指针直接赋给智能指针。
	unique_ptr<AA> pu2 = pu1;           // 错误,不能用其它unique_ptr拷贝构造。
	unique_ptr<AA> pu3;
	pu3 = pu1;                            // 错误,不能用=对unique_ptr进行赋值。

但是,你可以通过移动语义转移所有权:

unique_ptr<AA> p1 = make_unique<AA>("天梦");
unique_ptr<AA> p2 = std::move(p1); // p1 释放所有权,p2 成为新的所有者
  • 注意

  1. 不要用同一个原始指针(普通指针、裸指针)初始化多个unique_ptr对象。
  2. get()方法返回裸指针。
  3. 不要用unique_ptr管理不是new分配的内存。
  4. 不支持指针算术(即,不支持 +, -, ++, --)。

请看简单示例:

#include 
#include 
using namespace std;

class AA
{
public:
    string m_name;
    AA() { cout << m_name << "调用构造函数AA()。\n"; }
    AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
    ~AA() { cout << m_name << "调用了析构函数~AA(" << m_name << ")。\n"; }
};


int main() {
    AA* p = new AA("天梦");  // 定义原始指针p,分配内存并构造AA对象。
    unique_ptr<AA> pu(p);  // 创建智能指针对象,接管原始指针p的内存管理权。
    
    cout << "裸指针的值是: " << p << endl;  // 显示原始指针的内存地址。
    cout << "pu输出的结果是: " << pu.get() << endl;  // 显示智能指针管理的对象的内存地址。
    cout << "pu.get() 输出的结果是: " << pu.get() << endl;  // 同上,显示智能指针中存储的原始指针地址。
    cout << "pu的地址是: " << &pu << endl;  // 显示智能指针自身的地址。

    return 0;
}

其中pu输出的结果是智能指针管理的对象的内存地址。这是因为unique_ptr重载了operator<<,返回的是unique_ptr所指向的对象的内存地址,就像它是一个裸指针一样。

3. 用于函数的参数

unique_ptr 作为函数参数时,通常传递引用或者通过 std::move 转移所有权,因为它们不支持拷贝语义。

void some_function(unique_ptr<AA>& ptr) {
    // 不会转移所有权的函数
}

void some_function_take_ownership(unique_ptr<AA> ptr) {
    // 接受所有权的函数
}

// 调用
auto p = make_unique<AA>("天梦");
some_function(p);  // 传递引用,p 仍然拥有对象
some_function_take_ownership(std::move(p));  // 转移所有权,p 不再拥有对象

unique_ptr的使用技巧

  1. 将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做。如果源unique_ptr将存在一段时间,编译器禁止这样做。一般用于函数的返回值。

    unique_ptr<AA> p0;
    p0 = unique_ptr<AA>(new AA ("天梦")); //使用匿名对象赋值。
    

    我们使用移动赋值操作符将临时 unique_ptr 的所有权赋给 p0。在这个操作完成后,p0 现在拥有这个 AA 对象,而临时的 unique_ptr 不再拥有任何对象。

  2. nullptrunique_ptr赋值将释放对象,空的unique_ptr == nullptr

       unique_ptr<AA> p (new AA("天梦"));
       if(p != nullptr) cout<< "p is not null" << endl;
    
       p = nullptr;
       if(p == nullptr) cout<< "p is null" << endl;
    

    如果你使用 nullptrstd::unique_ptr 赋值,它将释放其当前所拥有的对象(如果有的话),并将内部指针设置为 nullptr。这可以被用作一种释放 unique_ptr 所拥有的资源的方法。

  3. release()释放对原始指针的控制权,将unique_ptr置为空,返回裸指针(可以用于把unique_ptr传递给子函数,子函数将负责释放对象)

  4. std::move()可以转移对原始指针的控制权。(可用于把unique_ptr传递给子函数,子函数形参也是unique_ptr)

    #include 
    #include 
    using  namespace std;
    
    class AA
    {
    public:
    	string m_name;
    	AA() { cout << m_name << "调用构造函数AA()。\n"; }
    	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
    	~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
    };
    
    // 函数func1()需要一个指针,但不对这个指针负责。
    void func1(const AA* a) {
    	cout << a->m_name << endl;
    }
    
    // 函数func2()需要一个指针,并且会对这个指针负责。
    void func2(AA* a) {
    	cout << a->m_name << endl;
    	delete a;
    }
    
    // 函数func3()需要一个unique_ptr,不会对这个unique_ptr负责。
    void func3(const unique_ptr<AA> &a) {
    	cout << a->m_name << endl;
    }
    
    // 函数func4()需要一个unique_ptr,并且会对这个unique_ptr负责。
    void func4(unique_ptr<AA> a) {
    	cout << a->m_name << endl;
    }
    
    int main()
    {
    	unique_ptr<AA> pu(new AA("tianmeng"));
    
    	cout << "开始调用函数。\n";
    	//func1(pu.get());        // 函数func1()需要一个指针,但不对这个指针负责。
    	//func2(pu.release());  // 函数func2()需要一个指针,并且会对这个指针负责。
    	//func3(pu);                // 函数func3()需要一个unique_ptr,不会对这个unique_ptr负责。
    	func4(move(pu));     // 函数func4()需要一个unique_ptr,并且会对这个unique_ptr负责。
    	cout << "调用函数完成。\n";
    
    	if (pu == nullptr) cout << "pu是空指针。\n";
    }
    
  5. reset()释放对象。

    void reset(T * _ptr= (T *) nullptr);
    pp.reset();        // 释放pp对象指向的资源对象。
    pp.reset(nullptr);  // 释放pp对象指向的资源对象
    pp.reset(new AA("bbb"));  // 释放pp指向的资源对象,同时指向新的对象。
    
  6. swap()交换两个unique_ptr的控制权

    pp1.swap(pp2); // 交换两个 unique_ptr 的所有权
    
  7. unique_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。

    #include 
    #include 
    using namespace std;
    
    class Base {
    public:
        virtual ~Base() = default;
        virtual void show() const {
            cout << "Base class" << endl;
        }
    };
    
    class Derived : public Base {
    public:
        void show() const override {
            cout << "Derived class" << endl;
        }
    };
    
    int main() {
        unique_ptr<Base> basePtr = make_unique<Derived>();
        basePtr->show();  // Outputs: Derived class
    
        return 0;
    }
    
  8. unique_ptr不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放。

    #include 
    #include 
    using  namespace std;
    
    class AA
    {
    public:
       string m_name;
       AA() { cout << m_name << "调用构造函数AA()。\n"; }
       AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
       ~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
    };
    
    unique_ptr<AA> p (new AA("全局变量"));
    
    int main(){
       unique_ptr<AA> p1(new AA("局部变量"));
    
       exit(0);
    }
    
  9. unique_ptr提供了支持数组的具体化版本。

    数组版本的unique_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。
    声明unique_ptr管理的数组:

    unique_ptr<int[]> myArray(new int[5]);
    

    或者使用现代的 std::make_unique 函数(从 C++14 开始支持数组版本):

    auto myArray = std::make_unique<int[]>(5);
    

    与裸指针数组类似,你可以使用 [] 操作符来访问数组中的每个元素。由于返回的是引用,你可以用它作为左值

shared_ptr

shared_ptr共享它所指向的对象,多个shared_ptr可以指向(关联)相同的的对象,并且在内部采用计数机制来实现。

当新的shared_ptr与对象关联时,引用计数加一。

shared_ptr超出作用域时,引用计数减1。当引用计数变为0时,则表示没有任何shared_ptr与对象关联,则释放该对象。

基本用法

初始化
  1. 方法一:直接使用构造函数

    shared_ptr<AA> p0(new AA("西施"));
    
  2. 方法二:使用mark_shared

    shared_ptr<AA> p0 = make_shared<AA>("天梦");  // C++11标准,效率更高。
    shared_ptr<int> pp1=make_shared<int>();         // 数据类型为int。
    shared_ptr<AA> pp2 = make_shared<AA>();       // 数据类型为AA,默认构造函数。
    shared_ptr<AA> pp3 = make_shared<AA>("天梦");  // 数据类型为AA,一个参数的构造函数。
    shared_ptr<AA> pp4 = make_shared<AA>("天梦",8); // 数据类型为AA,两个参数的构造函数。
    
  3. 方法三:采用已有的裸指针

    AA* p1 = new AA("天梦");
    shared_ptr<AA> p2(p1);
    
  4. 方法四:复制构造和赋值函数

    shared_ptr<AA> p3 = p0;  // 使用赋值操作符
    shared_ptr<AA> p4(p0);  // 使用复制构造函数
    
使用方法
  • 操作符重载shared_ptr 重载了 *->,这意味着你可以像使用裸指针一样使用它。
  • 引用计数:你可以使用 use_count() 方法来查询当前有多少个 shared_ptr 共享同一个对象。而 unique() 方法则检查是否只有一个 shared_ptr 指向该对象。
  • 赋值操作shared_ptr支持赋值,左值的shared_ptr的计数器将减1,右值shared_ptr的计算器将加1。
  • 获取裸指针get() 方法可以返回内部的裸指针,但通常应该避免使用它,除非真的需要裸指针。
  • 注意事项:不应该使用同一个裸指针来初始化多个独立的 shared_ptr,这会导致在程序结束时该对象被多次删除,从而引起未定义的行为。
用于函数的参数

unique_ptr相同

shared_ptr的使用技巧

  1. nullptrshared_ptr赋值将把计数减1,如果计数为0,将释放对象,空的shared_ptr==nullptr
  2. std::move()可以转移对原始指针的控制权。还可以将unique_ptr转移成shared_ptr
  3. reset()改变与资源的关联关系。
  4. swap()交换两个shared_ptr的控制权。
  5. shared_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。
  6. shared_ptr不是绝对安全,如果程序中调用exit()退出,全局的shared_ptr可以自动释放,但局部的shared_ptr无法释放。
  7. shared_ptr的引用计数是线程安全的,但在多线程环境中修改shared_ptr或其所指向的对象时仍需要加锁。
  8. 如果unique_ptr足以解决问题,那么优先选择unique_ptr。因为unique_ptrshared_ptr更高效,使用的资源也更少。

智能指针的删除器

在C++中,尽管智能指针可以自动删除所指向的对象,但在某些情况下,可能需要以特定的方式删除这些对象,例如,当对象是在非标准堆上分配的,或者与其他资源(如文件句柄或数据库连接)相关联时。这时,自定义删除器就派上了用场。

删除器是什么

默认情况下,智能指针,如shared_ptrunique_ptr,在其生命周期结束时使用delete操作符来释放其管理的对象。但是,你可以提供一个自定义的删除器,以便更改智能指针的行为。

删除器可以是

  • 全局函数:这是一个简单的函数,接受一个原始指针参数,并执行所需的清理操作。
  • 仿函数(Functor):这是一个定义了operator()的类或结构。它可以保持状态,并可以作为删除器使用。
  • Lambda表达式:这是一个简短的、可内联的匿名函数。它非常适合作为简短的删除器。

注意:智能指针使用删除器时,只要在模板签名里指定删除器的类型。

如:

unique_ptr <AA,*decltype*(deletefunc)*> p4(*new* AA("p4"),deletefunc);  *//* *普通函数*

下面是详细示例:

#include 
#include 
#include 
#include 

using namespace std;

class AA
{
public:
    string m_name;
    AA() { cout << m_name << "调用构造函数AA()。\n"; }
    AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
    ~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};

void deletefunc(AA *a){  // 自定义删除器   普通函数
    cout<<"自定义删除器(全局函数)"<<endl;
    delete a;
}

struct deleteclass{    // 自定义删除器 仿函数
    void operator()(AA *a){
        cout<<"自定义删除器(仿函数)"<<endl;
        delete a;
    }
};

auto deletelambda = [](AA *a){  // 自定义删除器 lambda表达式
    cout<<"自定义删除器(lambda表达式)"<<endl;
    delete a;
};

int main(){
    shared_ptr <AA> p1(new AA("p1"),deletefunc);  // 普通函数
    shared_ptr <AA> p2(new AA("p2"),deleteclass());  // 仿函数
    shared_ptr <AA> p3(new AA("p3"),deletelambda);  // lambda表达式
    
    unique_ptr <AA,decltype(deletefunc)*> p4(new AA("p4"),deletefunc);  // 普通函数
    unique_ptr <AA,void (*)(AA *)> p5(new AA("p5"),deletefunc);  // 普通函数
    unique_ptr <AA,deleteclass> p6(new AA("p6"));  // 仿函数
    unique_ptr <AA,decltype(deletelambda)> p7(new AA("p7"),deletelambda);  // lambda表达式
    
}

weak_ptr

shared_ptr存在的问题

首先,我们要明确shared_ptr的主要优点:自动引用计数。这意味着每当有一个新的shared_ptr指向同一个资源,这个资源的引用计数会增加,当某个shared_ptr不再存在,计数会减少。只有当引用计数为0时,资源才会被释放。然而,这种机制有一个重要的弱点,就是循环引用。

循环引用

循环引用发生在两个或多个shared_ptr对象相互引用,导致他们之间形成了一个引用环。在这种情况下,涉及到的所有对象的引用计数都不会将至为0,这就意味着这些对象永远不会被自动删除,从而导致内存泄漏。

#include 
#include 
using namespace std;

class B;

class A {
public:
    shared_ptr<B> b_ptr;
    ~A() {
        cout << "A destructor called!" << endl;
    }
};

class B {
public:
    shared_ptr<A> a_ptr;
    ~B() {
        cout << "B destructor called!" << endl;
    }
};

int main() {
    {
        shared_ptr<A> a = make_shared<A>();
        shared_ptr<B> b = make_shared<B>();

        a->b_ptr = b;  // A对象引用B对象
        b->a_ptr = a;  // B对象引用A对象
    }  // a和b的析构函数都不会被调用,因为存在循环引用

    cout << "End of main()" << endl;
}

在上述示例中,我们在A和B两个类中,分别创建了一个shared_ptr指向另一个类的对象,并在main函数中创建A和B的shared_ptr实例,互相引用。

由于A的实例持有指向Bshared_ptr,并且B的实例持有指向Ashared_ptr,所以两者形成了一个引用循环。当我们退出作用域后,尽管ab的生命周期已经结束,但由于存在循环引用,它们的引用计数都不为0,因此析构函数不会被调用,从而导致内存泄漏。

weak_ptr是什么

为了解决shared_ptr可能导致的循环引用问题,C++11引入了weak_ptr,与shared_ptr不同,weak_ptr不会增加或减少其所观察对象的引用计数。这意味着weak_ptr可以观察一个对象,但不会导致该对象的生命期延长或缩短。

功能和用途

  • weak_ptr主要与shared_ptr一起使用,以观察shared_ptr所拥有的资源。
  • weak_ptr观察的资源被销毁(例如,所有的shared_ptr都被销毁了)时,weak_ptr可以检测到这一点。
  • weak_ptrshared_ptr的一个"安全伙伴",使开发人员能够安全地管理复杂的对象关系,避免循环引用导致的内存泄漏。

如何使用weak_ptr

由于weak_ptr没有重载的->*操作符,您不能直接通过weak_ptr来访问其观察的对象。但您可以通过以下方式与weak_ptr互动:

  1. operator=():此操作允许您将一个shared_ptr或另一个weak_ptr赋值给当前的weak_ptr。这样,weak_ptr就可以观察(但不共享)该资源。
  2. expired():此函数用于检查weak_ptr指向的资源是否仍然存在。如果资源已经被销毁(即与之相关的所有shared_ptr都已经被销毁),则expired()返回true
  3. lock():这可能是weak_ptr中最常用的函数。它尝试获取指向对象的shared_ptr。如果对象仍然存在(即其引用计数不为0),lock()返回一个有效的shared_ptr。否则,它返回一个空的shared_ptr。这是一个线程安全的操作。
  4. reset():这将使weak_ptr不再指向任何对象,即它将其内部指针置为空。
  5. swap():此函数交换两个weak_ptr对象的内容。

使用weak_ptr的常见场景

  • 解决循环引用:如果两个对象互相引用并且使用shared_ptr,可能会出现循环引用的问题。在这种情况下,您可以使用weak_ptr来打破这种循环。
  • 缓存和池:如果您正在实现某种资源池或缓存,并希望知道某资源是否仍在使用(但不想阻止其被删除),weak_ptr是一个好选择。
  • 观察者模式:当对象需要被多个观察者观察,但不应该由观察者保持活跃时,可以使用weak_ptr

下面是一个简单示例:

#include 
#include 

using namespace std;

class Sample {
public:
    Sample() {
        cout << "Sample constructor called!" << endl;
    }
    ~Sample() {
        cout << "Sample destructor called!" << endl;
    }
};

int main() {
    // 使用 shared_ptr 创建一个 Sample 对象
    shared_ptr<Sample> sp1 = make_shared<Sample>();

    // 使用 weak_ptr 指向该 Sample 对象
    weak_ptr<Sample> wp1;

    // 1. operator=()
    wp1 = sp1; // 使用 operator=() 将 shared_ptr 赋值给 weak_ptr

    // 使用 weak_ptr 指向同一个 Sample 对象
    weak_ptr<Sample> wp2 = wp1;

    // 2. expired()
    if (wp1.expired()) {
        cout << "wp1 is expired." << endl;
    } else {
        cout << "wp1 is not expired." << endl;
    }

    // 3. lock()
    shared_ptr<Sample> sp2 = wp1.lock(); // 提升 weak_ptr 为 shared_ptr
    if (sp2) {
        cout << "Successfully locked wp1!" << endl;
    }

    // 释放 sp1 指向的资源
    sp1.reset();

    if (wp1.expired()) {
        cout << "wp1 is now expired." << endl;
    }

    // 4. reset()
    wp2.reset(); // 将 wp2 置为空

    if (wp2.expired()) {
        cout << "wp2 is expired." << endl;
    }

    // 5. swap()
    wp1.swap(wp2); // 交换 wp1 和 wp2 的内容

    if (wp2.expired()) {
        cout << "After swap, wp2 is still expired." << endl;
    }
    if (wp1.expired()) {
        cout << "After swap, wp1 is now expired." << endl;
    }

    return 0;
}

你可能感兴趣的:(C++学习/笔记,算法,microsoft)