C++关于智能指针的一些常见问题

首先解释指针和引用在C++中都用于间接访问变量,但它们有一些区别:

1. 指针是一个变量,它保存了另一个变量得内存地址;引用是另一个变量的别名,与原变量共享内存地址。

2. 指针可以被重新赋值,指向不同的变量;引用在初始化后不能更改,始终指向同一个变量。

3. 指针可以为nullptr,表示不指向任何变量;引用必须绑定到一个变量,不能为nullptr。

4. 使用指针需要对其进行解引用以获取或修改其指向的变量的值;引用可以直接使用,无需解引用。

在汇编层面来看    引用会被C++编译器当做const指针来进行操作。

RAII(Resource Acquisition Is Initialization)是由C++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,他说:使用局部对象来管理资源的技术称为资源获取即初始化;

这里的资源主要是指操作系统中有限的东西如指针内存、网络套接字、文件等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。

C/C++常见的内存错误

在C/C++中,使用指针会出现各种问题,比如:

  1. 野指针:未初始化或已经被释放的指针被称为野指针
  2. 空指针:指向空地址的指针被称为空指针
  3. 内存泄露:如果在使用完动态分配的内存后忘记释放,就会造成内存泄露,长时间运行的程序可能会消耗大量内存
  4. 悬空指针:指向已经释放的内存的指针被称为悬空指针
  5. 内存泄露和悬空指针的混合:在一些情况下,由于内存泄露和悬空指针共同存在,程序可能会出现异常行为

智能指针

智能指针是一种可以自动管理内存的指针,它可以在不需要手动释放内存的情况下,确保对象正确地销毁。

这种指针可以显著降低程序中的内存泄露和悬空指针的风险。

智能指针的核心思想就是RAII

在C++中主要有两种智能指针

std::unique_ptr

std::shared_ptr

std::unique_ptr

std::unique_ptr是一个独占所有权的智能指针,它保证指向的内存只能有一个unique_ptr拥有,不能共享所有权。

std::shared_ptr

std::shared_ptr是一个共享所有权的智能指针,它允许多个shared_ptr指向同一个对象,当最后一个shared_ptr超出超出作用域时,所指向的内存才会被自动释放。

shared_ptr通过引用计数来记录有多少个shared_ptr共享同一个对象。

#include 
#include 

class MyClass {
public:
    MyClass() { std::cout << "MyClass 构造函数\n"; }
    ~MyClass() { std::cout << "MyClass 析构函数\n"; }
    void do_something() { std::cout << "MyClass::do_something() 被调用\n"; }
};

int main() {
    {
        std::shared_ptr ptr1 = std::make_shared();
        {
            std::shared_ptr ptr2 = ptr1; // 这里共享 MyClass 对象的所有权
            ptr1->do_something();
            ptr2->do_something();
            std::cout << "ptr1 和 ptr2 作用域结束前的引用计数: " << ptr1.use_count() << std::endl;
        } // 这里 ptr2 被销毁,但是 MyClass 对象不会被删除,因为 ptr1 仍然拥有它的所有权
        std::cout << "ptr1 作用域结束前的引用计数: " << ptr1.use_count() << std::endl;
    } // 这里 ptr1 被销毁,同时 MyClass 对象也会被删除,因为它是最后一个拥有对象所有权的 shared_ptr

    return 0;
}
MyClass 构造函数
MyClass::do_something() 被调用
MyClass::do_something() 被调用
ptr1 和 ptr2 作用域结束前的引用计数: 2
ptr1 作用域结束前的引用计数: 1
MyClass 析构函数

 

shared_ptr的double free问题

double free 问题就是一块内存空间或者资源被释放两次。那么为什么会释放两次呢?

double free 可能是下面这些原因造成的:

  • 直接使用原始指针创建多个 shared_ptr,而没有使用 shared_ptr 的 make_shared 工厂函数,从而导致多个独立的引用计数。
  • 循环引用,即两个或多个 shared_ptr 互相引用,导致引用计数永远无法降为零,从而无法释放内存。


解决 shared_ptr double free 问题的方法:

  • 使用 make_shared 函数创建 shared_ptr 实例,而不是直接使用原始指针。这样可以确保所有 shared_ptr 实例共享相同的引用计数。
  • 对于可能产生循环引用的情况,使用 weak_ptr。weak_ptr 是一种不控制对象生命周期的智能指针,它只观察对象,而不增加引用计数。这可以避免循环引用导致的内存泄漏问题。

shared_ptr 常用 API

shared_ptr 构造函数:创建一个空的 shared_ptr,不指向任何对象。

std::shared_ptr ptr;


make_shared(args...):创建一个 shared_ptr,并在单次内存分配中同时创建对象和控制块。这比直接使用 shared_ptr 的构造函数要高效。

std::shared_ptr ptr = std::make_shared(42);


reset():释放当前 shared_ptr 的所有权,将其设置为 nullptr。如果当前 shared_ptr 是最后一个拥有对象所有权的智能指针,则会删除对象。

ptr.reset();


reset(T*):释放当前 shared_ptr 的所有权,并使其指向新的对象。如果当前 shared_ptr 是最后一个拥有对象所有权的智能指针,则会删除原对象。

ptr.reset(new int(42));


get():返回指向的对象的裸指针。注意,这个裸指针的生命周期由 shared_ptr 管理,你不应该使用它来创建另一个智能指针。

int* raw_ptr = ptr.get();


operator* 和 operator->:访问指向的对象。

int value = *ptr;
std::shared_ptr> vec_ptr = std::make_shared>();
vec_ptr->push_back(42);


use_count():返回当前 shared_ptr 的引用计数,即有多少个 shared_ptr 共享同一个对象。注意,use_count() 通常用于调试,不应该用于程序逻辑。

size_t count = ptr.use_count();


unique():检查当前 shared_ptr 是否是唯一拥有对象所有权的智能指针。等价于 use_count() == 1。

bool is_unique = ptr.unique();


swap(shared_ptr&):交换两个 shared_ptr 的内容。

std::shared_ptr ptr1 = std::make_shared(42);
std::shared_ptr ptr2 = std::make_shared(24);
ptr1.swap(ptr2);


operator bool():将 shared_ptr 隐式转换为 bool 类型,用于检查其是否为空。

if (ptr) {
    std::cout << "ptr 不为空" << std::endl;
} else {
    std::cout << "ptr 为空" << std::endl;
}

文章参考
作者: 编程指北
链接: https://csguide.cn/cpp/memory/shared_ptr.html#shared-ptr-%E7%9A%84%E4%BD%BF%E7%94%A8
来源: https://csguide.cn

你可能感兴趣的:(C++基础概念,jvm)