【C++】智能指针 学习总结 |std::shared_ptr |std::unique_ptr | std::weak_ptr


前言

参考答案:chatgpt

一、智能指针介绍

智能指针是C++的一种对象,它可以像常规指针那样,用来指向堆上的数据。然而,智能指针的关键优势在于,**当你不再需要在堆上的数据时,它们可以自动删除。**这是通过在智能指针的析构函数中调用delete来实现的,当智能指针的生命周期结束时(例如,它离开了它的作用域),它的析构函数会被自动调用。
C++标准库提供了几种智能指针:

std::auto_ptr:这是C++98标准的一部分,但在C++11中已被弃用,C++17中已被移除。它具有所有权传递语义,意味着复制auto_ptr会改变原有auto_ptr的状态(会失去指向对象的能力)。

std::unique_ptr:这是C++11引入的智能指针,它表现出独占所有权语义,意味着在任何时间,只有一个unique_ptr可以指向给定的对象。当unique_ptr被销毁(例如,离开其作用域)时,它所指向的对象也会被delete掉。
C++14 开始,建议使用 std::make_unique 函数来创建 std::unique_ptr,

auto p = std::make_unique<MyClass>();

std::shared_ptr:这也是C++11引入的智能指针,允许多个shared_ptr指向同一个对象。shared_ptr维护了一个引用计数,当最后一个shared_ptr被销毁(离开其作用域)时,它所指向的对象才会被delete掉。

std::weak_ptr:这也是C++11引入的智能指针,它是对shared_ptr的补充和配合。weak_ptr指向一个由shared_ptr管理的对象,但不会增加该对象的引用计数。这主要用于解决shared_ptr的循环引用问题。

后面三个智能指针用的比较多。
使用智能指针可以帮助管理动态内存,避免内存泄漏。然而,虽然智能指针在许多情况下都非常有用,但它们并不能解决所有内存管理问题,例如,它们无法解决循环引用的问题(除非结合使用weak_ptr)。所以,理解并妥善使用智能指针依然非常重要。

#include < memory>,这个头文件在 C++ 中包含了许多关于内存管理的工具,包括智能指针如 std::unique_ptr 和 std::shared_ptr。


回忆一下,什么是内存泄露?
内存泄漏是指程序中已经动态分配的堆内存由于某种原因程序未释放或者无法释放,导致系统内存的浪费,降低系统性能的现象。如果程序持续运行,内存泄漏会导致系统可用内存逐渐下降,最终可能因为没有足够的内存可用而导致系统崩溃。
例如,以下是一个在 C++ 中产生内存泄漏的简单例子:

#include <iostream>

int main() {
    
    
    while (true) {
    
    
        int* p = new int[100];  // 分配内存但未释放
    }
    return 0;
}

在这个例子中,程序在一个无限循环中不断地分配内存,但从未释放它。因此,它会不断消耗系统内存,最终可能导致系统崩溃。
解决内存泄漏的方法通常涉及找出程序中没有正确释放的内存,然后修改代码以确保内存在使用后被正确释放。在一些现代编程语言中,如 Java、Python、C#、Go 等,提供了垃圾收集机制,能够自动回收不再使用的内存,从而避免内存泄漏。在 C++ 中,使用智能指针(如 std::unique_ptr 或 std::shared_ptr)也可以帮助管理内存并避免内存泄漏。

二、普通指针和智能指针的比较案例

一个原始指针代码如下:

#include <iostream>

struct MyClass {
    
    
    MyClass() {
    
     std::cout << "MyClass constructed\n"; }
    ~MyClass() {
    
     std::cout << "MyClass destroyed\n"; }
};

int main() {
    
    
    MyClass* p = new MyClass();  // 创建 MyClass 实例

    // 必须显式调用 delete 来释放内存
    delete p;

    return 0;
}

在这里插入图片描述

在上述代码中,你需要手动管理内存。你必须显式调用 delete 来释放 p 所指向的内存。如果你忘记了,那就会导致内存泄漏。

智能指针:

#include <iostream>
#include <memory>

struct MyClass {
    
    
    MyClass() {
    
     std::cout << "MyClass constructed\n"; }
    ~MyClass() {
    
     std::cout << "MyClass destroyed\n"; }
};

int main() {
    
    
    std::unique_ptr<MyClass> p(new MyClass());  // 创建 MyClass 实例

    // 不需要显式调用 delete 来释放内存
    // 当 p 离开作用域时,它的析构函数会自动被调用,释放内存

    return 0;
}

std::unique_ptr:这是一个 std::unique_ptr 模板类的实例,其中的 MyClass 是模板参数,指定了这个智能指针可以指向 MyClass 类型的对象。

p:这是我们为智能指针变量取的名字。

new MyClass():这是使用 new 关键字创建一个新的 MyClass 对象的实例,并返回其内存地址。

std::unique_ptr p(new MyClass());:整个这句代码就是创建一个名为 p 的 std::unique_ptr 对象,它将管理我们新创建的 MyClass 对象的内存。

这里的p()括号里是初始化的意思、std::unique_ptr p(new MyClass()); 中的 new MyClass() 是用来初始化 std::unique_ptr 的。这个表达式创建了一个 MyClass 类型的对象,并返回了指向这个新创建对象的指针。然后,这个指针被用来初始化 std::unique_ptr。

最后结果:
在这里插入图片描述


接下来介绍那几种关键的智能指针

三、std::shared_ptr

下面给出一种用法:

#include <iostream>
#include <memory>

struct MyClass {
    
    
    MyClass() {
    
     std::cout << "MyClass constructed\n"; }
    ~MyClass() {
    
     std::cout << "MyClass destroyed\n"; }
};

void observe(std::shared_ptr<MyClass> p) {
    
    
    std::cout << "Inside observe function: shared_ptr use_count: " << p.use_count() << "\n";
}

int main() {
    
    
    // 使用 std::make_shared 创建 shared_ptr
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    std::cout << "After ptr1 creation: shared_ptr use_count: " << ptr1.use_count() << "\n";
    
    {
    
    
        // 创建一个新的 shared_ptr,共享 MyClass 对象的所有权
        std::shared_ptr<MyClass> ptr2 = ptr1;
        std::cout << "After ptr2 creation: shared_ptr use_count: " << ptr1.use_count() << "\n";
        
        observe(ptr2);
    }  // ptr2 离开作用域,MyClass 对象的所有权计数减一
    std::cout << "After ptr2 destruction: shared_ptr use_count: " << ptr1.use_count() << "\n";

    // 现在只有 ptr1 拥有 MyClass 对象
}  // ptr1 离开作用域,MyClass 对象的所有权计数归零,MyClass 对象被删除

这段代码的主要目标是展示 std::shared_ptr 的用法和行为。std::shared_ptr 是一个智能指针,它允许多个指针共享同一个对象的所有权。
下面是对每一部分的详细解释:

首先定义了一个名为 MyClass 的结构体,它有一个构造函数和一个析构函数。构造函数和析构函数都会在被调用时输出一段信息。

observe 是一个函数,它接受一个 std::shared_ptr 作为参数,并输出该指针的 use_count(),即该指针当前的引用计数(共享所有权的指针数量)。

main 函数是这段代码的主要部分,展示了 std::shared_ptr 的使用:

使用 std::make_shared 创建了一个新的 std::shared_ptr,名为 ptr1,并使其指向一个新的 MyClass 实例。然后输出 ptr1 的 use_count(),此时应为 1,因为只有 ptr1 指向这个 MyClass 实例。

在一个新的作用域内(由 {} 定义),创建了一个新的 std::shared_ptr,名为 ptr2,并使其也指向 ptr1 所指向的 MyClass 实例。然后输出 ptr1 的 use_count(),此时应为 2,因为 ptr1 和 ptr2 都指向这个 MyClass 实例。

在同一作用域内,调用 observe 函数,并将 ptr2 作为参数传入。observe 函数会输出 ptr2 的 use_count(),此时也应为 2。

当这个作用域结束(即到达 }),ptr2 超出其作用域并被销毁。此时 MyClass 实例的引用计数减一。然后输出 ptr1 的 use_count(),此时应为 1,因为只剩下 ptr1 指向这个 MyClass 实例。

当 main 函数结束,ptr1 也超出其作用域并被销毁。此时 MyClass 实例的引用计数再次减一,变为 0,因此 MyClass 实例被删除,并调用其析构函数。

总的来说,这段代码展示了如何使用 std::shared_ptr 来共享一个对象的所有权,并在所有指向该对象的 std::shared_ptr 都被销毁时自动删除该对象。

最后:
在这里插入图片描述

四、std::unique_ptr

下面是一个例子:

#include <iostream>
#include <memory>

struct MyClass {
    
    
    MyClass() {
    
     std::cout << "MyClass constructed\n"; }
    ~MyClass() {
    
     std::cout << "MyClass destroyed\n"; }
};

void observe(std::unique_ptr<MyClass>& p) {
    
    
    if (p != nullptr) {
    
    
        std::cout << "Inside observe function: unique_ptr is not nullptr\n";
    } else {
    
    
        std::cout << "Inside observe function: unique_ptr is nullptr\n";
    }
}

int main() {
    
    
    // 使用 std::make_unique 创建 unique_ptr
    std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
    observe(ptr1);

    // 通过 std::move 将所有权从 ptr1 转移给 ptr2
    std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
    observe(ptr1);
    observe(ptr2);
}  // MyClass 对象被销毁,因为 ptr2 超出其作用域

在这段代码中,首先使用 std::make_unique 创建了一个 std::unique_ptr,名为 ptr1,并使其指向一个新的 MyClass 实例。然后调用 observe 函数来检查 ptr1 是否为 nullptr。
接下来,通过 std::move 将所有权从 ptr1 转移给新的 std::unique_ptr,名为 ptr2。此时 ptr1 变为 nullptr,因为所有权已经转移走了。
再次调用 observe 函数来检查 ptr1 和 ptr2。此时,ptr1 应为 nullptr,而 ptr2 不应为 nullptr。
当 main 函数结束时,ptr2 也超出其作用域并被销毁。此时,MyClass 实例也被删除,并调用其析构函数。这是因为 std::unique_ptr 在超出作用域时会自动删除其所指向的对象。

在这里插入图片描述

五、std::weak_ptr

std::weak_ptr 是一个用于解决 std::shared_ptr 循环引用问题的智能指针。std::weak_ptr 对于对象的所有权是无影响的,只是提供了一种访问共享对象的方式,不会增加引用计数。
以下是一个 std::weak_ptr 的使用例子:

#include <iostream>
#include <memory>

struct MyClass {
    
    
    MyClass() {
    
     std::cout << "MyClass constructed\n"; }
    ~MyClass() {
    
     std::cout << "MyClass destroyed\n"; }
};

void observe(std::weak_ptr<MyClass> p) {
    
    
    if (auto spt = p.lock()) {
    
      // Must be copied into a shared_ptr before usage
        std::cout << "Inside observe function: weak_ptr is not expired, shared_ptr use_count: " << spt.use_count() << "\n";
    } else {
    
    
        std::cout << "Inside observe function: weak_ptr is expired\n";
    }
}

int main() {
    
    
    std::weak_ptr<MyClass> wptr;

    {
    
    
        // 使用 std::make_shared 创建 shared_ptr
        std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
        wptr = ptr;
        observe(wptr);
    }  // ptr 离开作用域,MyClass 对象被销毁

    observe(wptr);  // wptr 已经过期,因为 MyClass 对象已经被销毁
}

在这里插入图片描述
在这段代码中,首先定义了一个 std::weak_ptr,名为 wptr。
然后在一个新的作用域内(由 {} 定义),创建了一个 std::shared_ptr,名为 ptr,并使其指向一个新的 MyClass 实例。然后将 ptr 赋值给 wptr,使 wptr 也指向 ptr 所指向的 MyClass 实例。
接着调用 observe 函数,将 wptr 作为参数传入。在 observe 函数内部,通过调用 std::weak_ptr::lock 来获取一个指向 MyClass 实例的 std::shared_ptr,并输出其 use_count()。
当这个作用域结束(即到达 }),ptr 超出其作用域并被销毁,MyClass 实例的引用计数减一,变为 0,因此 MyClass 实例被删除,并调用其析构函数。
最后,再次调用 observe 函数,将 wptr 作为参数传入。此时 wptr 已经过期,因为 MyClass 实例已经被销毁,因此 std::weak_ptr::lock 会返回一个空的 std::shared_ptr。

六、std::shared_ptr |std::unique_ptr | std::weak_ptr三大智能指针的区别

std::shared_ptr, std::unique_ptr 和 std::weak_ptr 都是 C++11 引入的智能指针,它们的主要目标是提供自动化的内存管理,以避免内存泄漏。然而,它们之间的区别主要在于如何管理内存以及所有权语义:

std::shared_ptr:这是一种引用计数的智能指针。当你将一个对象的指针赋值给一个 std::shared_ptr 时,它就会开始进行引用计数。如果有其他 std::shared_ptr 指向同一对象,引用计数就会增加。当引用计数变为0(即没有 std::shared_ptr 指向该对象时),对象就会被自动删除。

std::unique_ptr:这种智能指针保证了对象的唯一所有权语义。在任何时候,只有一个 std::unique_ptr 可以指向一个对象。当 std::unique_ptr 超出作用域或被重新赋值时,其原来指向的对象就会被自动删除。如果你需要将所有权转移给另一个 std::unique_ptr,你可以使用 std::move。

std::weak_ptr:这是一种特殊的智能指针,主要用来解决 std::shared_ptr 的循环引用问题。std::weak_ptr 本身不对对象的生命周期造成影响,也就是说,拥有一个指向对象的 std::weak_ptr 不会阻止对象被删除。它只是为了提供一种访问已经存在(并且被 std::shared_ptr 管理)的对象的方式,而不会增加引用计数。要使用 std::weak_ptr,你需要先将其升级为 std::shared_ptr,如果此时对象仍然存在,升级操作就会成功。

这些智能指针都是 C++ 标准库的一部分,设计用来自动管理内存,以避免常规指针可能导致的内存泄漏和其他问题。在编写 C++ 程序时,应优先考虑使用这些智能指针,而不是裸指针。
小插曲:
今天用vscode的时候 调试控制台不输出信息了 比如我print你好,他竟然不输出,只输出几个线程的信息,高的很是烦人,后面找到了解决方案:解决方法

猜你喜欢

转载自blog.csdn.net/weixin_46274756/article/details/131130734