C++ std::weak_ptr智能指针详解(附带实例)
在 C++ 的 std::shared_ptr 出现之前,如果有两个指针指向同一个对象,当一个指针被销毁时,对象也会被销毁,但还有另一个指针指向这个对象,如果再次销毁对象就会出现错误。std::shared_ptr 通过引用计数解决了这个问题,但这也带来了新的问题。
当一个 std::shared_ptr 被销毁时,如果它是指向一个被另一个 std::shared_ptr 所拥有的对象,则这个对象的引用计数就会减一,由于此计数变为 0,所以这个对象会被销毁。这就是所谓的“悬垂指针”(Dangling Pointer)。
另外,如果有两个对象互相引用对方,并且都使用 std::shared_ptr 来管理,则这两个对象的引用计数永远都不会为0,即使它们实际上已经不再被使用。这是因为每次一个对象被销毁时,它都会减少另一个对象的引用计数,这将导致另一个对象的引用计数永远不会为 0。
先来看一个错误使用 shared_ptr 的示例,代码如下:
在 main() 函数中,分别调用 a 和 b 的成员函数 doSomething(),输出相应的结果,然而,当 a 和 b 的引用计数都降为 0 时,它们无法被正确地释放,从而导致内存泄漏。
为了解决这个问题,需要使用 std::weak_ptr 来打破循环引用。
std::weak_ptr 用于共享对象,但不会增加引用计数,因此,它不会影响对象的生命周期。std::weak_ptr 指向的对象由 std::shared_ptr 管理。当最后一个 std::shared_ptr 对象被销毁时,如果没有其他 std::weak_ptr 对象指向该对象,则该对象会被销毁,否则该对象将继续存在。
std::weak_ptr 不支持对所指对象进行直接访问,需要通过 lock() 函数获取一个 std::shared_ptr 对象访问所指对象。lock() 函数会检查所指对象是否存在,如果存在,则返回一个指向该对象的 std::shared_ptr,否则返回一个空的 std::shared_ptr。
使用 std::weak_ptr 的一个常见场景是解决循环引用问题。当两个或多个对象相互引用且其中至少一个对象使用 std::shared_ptr 来管理资源时,可能会出现循环引用。这会导致资源无法释放,从而造成内存泄漏。
将其中一个对象的句柄使用 std::weak_ptr 而不是 std::shared_ptr,在销毁其中的一个对象时,它的 std::shared_ptr 计数将减少,但不会阻止对象的销毁。这样可以打破循环引用,从而释放资源。
需要注意的是,由于 std::weak_ptr 不会增加引用计数,所以在使用 lock() 函数获取 std::shared_ptr 时,一定要对返回的指针进行有效性检查以确保所指对象仍然存在。
以下是一个简单的示例,代码如下:
当一个 std::shared_ptr 被销毁时,如果它是指向一个被另一个 std::shared_ptr 所拥有的对象,则这个对象的引用计数就会减一,由于此计数变为 0,所以这个对象会被销毁。这就是所谓的“悬垂指针”(Dangling Pointer)。
另外,如果有两个对象互相引用对方,并且都使用 std::shared_ptr 来管理,则这两个对象的引用计数永远都不会为0,即使它们实际上已经不再被使用。这是因为每次一个对象被销毁时,它都会减少另一个对象的引用计数,这将导致另一个对象的引用计数永远不会为 0。
先来看一个错误使用 shared_ptr 的示例,代码如下:
#include <iostream> #include <memory> class B; class A { private: std::shared_ptr<B> b_ptr; public: void setB(const std::shared_ptr<B> & b) { b_ptr = b; } void doSomething() { std::cout << "Doing something in A" << std::endl; } }; class B { private: std::shared_ptr<A> a_ptr; public: void setA(const std::shared_ptr<A> & a) { a_ptr = a; } void doSomething() { std::cout << "Doing something in B" << std::endl; } }; int main() { std::shared_ptr<A> a = std::make_shared<A>(); std::shared_ptr<B> b = std::make_shared<B>(); //交叉引用,释放后会造成指针的错误释放 a->setB(b); b->setA(a); a->doSomething(); b->doSomething(); return 0; }在这个示例中,类 A 和类 B 相互引用,并且使用 std::shared_ptr 来管理资源。创建了两个 std::shared_ptr 对象 a 和 b,并将它们分别传递给类 A 和类 B 的成员函数 setB() 和 setA()。这样,a 和 b 之间就形成了循环引用。
在 main() 函数中,分别调用 a 和 b 的成员函数 doSomething(),输出相应的结果,然而,当 a 和 b 的引用计数都降为 0 时,它们无法被正确地释放,从而导致内存泄漏。
为了解决这个问题,需要使用 std::weak_ptr 来打破循环引用。
std::weak_ptr 用于共享对象,但不会增加引用计数,因此,它不会影响对象的生命周期。std::weak_ptr 指向的对象由 std::shared_ptr 管理。当最后一个 std::shared_ptr 对象被销毁时,如果没有其他 std::weak_ptr 对象指向该对象,则该对象会被销毁,否则该对象将继续存在。
std::weak_ptr 不支持对所指对象进行直接访问,需要通过 lock() 函数获取一个 std::shared_ptr 对象访问所指对象。lock() 函数会检查所指对象是否存在,如果存在,则返回一个指向该对象的 std::shared_ptr,否则返回一个空的 std::shared_ptr。
使用 std::weak_ptr 的一个常见场景是解决循环引用问题。当两个或多个对象相互引用且其中至少一个对象使用 std::shared_ptr 来管理资源时,可能会出现循环引用。这会导致资源无法释放,从而造成内存泄漏。
将其中一个对象的句柄使用 std::weak_ptr 而不是 std::shared_ptr,在销毁其中的一个对象时,它的 std::shared_ptr 计数将减少,但不会阻止对象的销毁。这样可以打破循环引用,从而释放资源。
需要注意的是,由于 std::weak_ptr 不会增加引用计数,所以在使用 lock() 函数获取 std::shared_ptr 时,一定要对返回的指针进行有效性检查以确保所指对象仍然存在。
以下是一个简单的示例,代码如下:
#include <iostream> #include <memory> class B; // 前向声明 class A { private: std::weak_ptr<B> b_ptr; public: void setB(const std::shared_ptr<B>& b) { b_ptr = b; } void doSomething(); // 先声明,稍后定义(放在 B 完整定义之后) }; class B { private: std::weak_ptr<A> a_ptr; public: void setA(const std::shared_ptr<A>& a) { a_ptr = a; } void doSomething() { std::shared_ptr<A> a = a_ptr.lock(); if (a) { std::cout << "Doing something in B" << std::endl; } else { std::cout << "A does not exist anymore" << std::endl; } } }; // ?? 这里是 A::doSomething() 的定义,放到 B 的定义之后 void A::doSomething() { std::shared_ptr<B> b = b_ptr.lock(); if (b) { std::cout << "Doing something in A" << std::endl; b->doSomething(); // 现在可以访问 B 的成员函数了 } else { std::cout << "B does not exist anymore" << std::endl; } } int main() { std::shared_ptr<A> a = std::make_shared<A>(); std::shared_ptr<B> b = std::make_shared<B>(); a->setB(b); b->setA(a); a->doSomething(); // 输出 "Doing something in A" 和 "Doing something in B" a = nullptr; // 销毁 A b->doSomething(); // 输出 "A does not exist anymore" b = nullptr; // 销毁 B return 0; }在这个示例中,类 A 和类 B 相互引用,并且使用 std::shared_ptr 来管理资源。通过在类 A 和类 B 中使用 std::weak_ptr 来引用对方,可以打破循环引用。当其中的一个对象被销毁时,另一个对象可以安全地访问 std::weak_ptr,以此来判断对方是否还存在,并进行相应操作。