C++ 智能指针shared_ptr和weak_ptr


C++智能指针

C++11 引入了 3 个智能指针类型用于解决指针管理问题,防止内存泄露:

  1. unique_ptr
  2. shared_ptr
  3. weak_ptr

智能指针是现代C++中管理动态分配内存的重要工具,它们通过自动管理资源的生命周期,帮助开发者避免常见的内存管理错误,如内存泄漏、野指针等。

unique_ptr

`unique_ptr` 是一种独占所有权的智能指针。当我们独占资源的所有权时,可以使用 `std::unique_ptr` 对资源进行管理。当 `unique_ptr` 对象离开其作用域时,它会自动释放所管理的资源。这是基于RAII(资源获取即初始化)思想的典型应用。

例如:

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr ptr(new int(42));
    std::cout << *ptr << std::endl; // 输出42
    return 0; // unique_ptr离开作用域,自动释放资源
}

`unique_ptr` 不能被复制,但可以被移动。这意味着它非常适合用于那些需要独占资源所有权的场景。

shared_ptr

`shared_ptr` 允许多个智能指针共享同一块内存资源。它使用引用计数来跟踪有多少个智能指针指向相同的资源。只有当最后一个 `shared_ptr` 被销毁时,资源才会被释放。

例如:

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr ptr1 = std::make_shared(42);
    std::shared_ptr ptr2 = ptr1; // 共享所有权
    std::cout << ptr2.use_count() << std::endl; // 输出2
    std::cout << ptr1.use_count() << std::endl; // 输出2
    return 0;
}

在这个例子中,`ptr1` 和 `ptr2` 都指向同一块内存资源。`use_count()` 方法返回当前指向该资源的 `shared_ptr` 的数量。当引用计数为 0 时,资源会被自动释放。

weak_ptr

`weak_ptr` 通常与 `shared_ptr` 一起使用,用于解决循环引用的问题。`weak_ptr` 不增加引用计数,只是观察 `shared_ptr` 的生命周期,不会阻止资源的释放。

例如:

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr ptr = std::make_shared(42);
    std::weak_ptr weakPtr = ptr;
    if (auto sharedPtr = weakPtr.lock()) {
        std::cout << *sharedPtr << std::endl; // 输出42
    }
    return 0; // 在这里ptr被销毁
}

在这个例子中,`weakPtr` 是一个 `weak_ptr`,它指向由 `ptr` 管理的资源。`weakPtr.lock()` 方法尝试获取一个指向资源的 `shared_ptr`。如果资源仍然存在,它会返回一个有效的 `shared_ptr`;否则,它会返回一个空的 `shared_ptr`。

解决循环引用问题

循环引用是 `shared_ptr` 使用中常见的问题。例如,两个对象相互引用对方的 `shared_ptr`,会导致引用计数永不为零,从而导致内存泄漏。

例如:

#include <iostream>
#include <memory>

class B; // forward declaration

class A {
public:
    std::shared_ptr bPtr; // shared_ptr to B
    ~A() {
        std::cout << "A destructor called" << std::endl;
    }
};

class B {
public:
    std::shared_ptr aPtr; // shared_ptr to A
    ~B() {
        std::cout << "B destructor called" << std::endl;
    }
};

int main() {
    std::shared_ptr aPtr = std::make_shared(); // create a shared_ptr to A
    std::shared_ptr bPtr = std::make_shared(); // create a shared_ptr to B
    aPtr->bPtr = bPtr; // assign bPtr to bPtr member of A
    bPtr->aPtr = aPtr; // assign aPtr to aPtr member of B
    return 0; // 由于循环引用,析构函数不会被调用,导致内存泄漏
}

在这个例子中,`A` 和 `B` 相互引用对方的 `shared_ptr`,导致引用计数永不为零,从而导致内存泄漏。

使用 `weak_ptr` 可以解决这个问题:

#include <iostream>
#include <memory>

class B; // forward declaration

class A {
public:
    std::shared_ptr bPtr; // shared_ptr to B
    ~A() {
        std::cout << "A destructor called" << std::endl;
    }
};

class B {
public:
    std::weak_ptr aWeakPtr; // weak_ptr to A
    ~B() {
        std::cout << "B destructor called" << std::endl;
    }
};

int main() {
    std::shared_ptr aPtr = std::make_shared(); // create a shared_ptr to A
    std::shared_ptr bPtr = std::make_shared(); // create a shared_ptr to B
    aPtr->bPtr = bPtr; // assign bPtr to bPtr member of A
    bPtr->aWeakPtr = aPtr; // assign aPtr to aWeakPtr member of B
    return 0; // 现在A和B的析构函数都会被正确调用
}

在这个修改后的例子中,`B` 使用 `weak_ptr` 来引用 `A`,从而避免了循环引用问题。当 `aPtr` 和 `bPtr` 离开作用域时,它们的析构函数都会被正确调用,资源也会被正确释放。

总结

C++11 引入的智能指针是现代C++中管理动态内存的重要工具。`unique_ptr` 适用于独占资源所有权的场景,`shared_ptr` 适用于多个指针共享资源的场景,而 `weak_ptr` 则用于解决循环引用问题。通过合理使用这些智能指针,可以有效避免内存泄漏和其他资源管理问题,使代码更加安全和易于维护。