好的,各位观众老爷,今天咱来聊聊 C++ 里的弱指针和 enable_shared_from_this
。这俩家伙,听起来好像是武林秘籍,实际上是解决 C++ 智能指针里一个很常见,也很让人头疼的问题——循环引用。
啥是循环引用?
简单来说,就是两个或多个对象互相持有对方的 shared_ptr
。这就形成了一个闭环,谁也释放不了谁,最终导致内存泄漏。想象一下,你和你的朋友,你俩都拽着对方的胳膊,谁也不撒手,结果就是谁也走不了,只能原地尬住。
举个栗子:
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destructor called" << std::endl; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B destructor called" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
std::cout << "程序结束" << std::endl;
return 0;
}
运行这段代码,你会发现 A 和 B 的析构函数都没有被调用。这就是循环引用在作妖! a
持有 b
, b
持有 a
,引用计数永远大于 0,内存永远无法释放。
弱指针:解开循环引用的钥匙
弱指针(std::weak_ptr
)就是来解开这个死结的。它就像一根细线,指向一个对象,但不增加对象的引用计数。你可以用它来观察对象是否还活着,如果对象已经被销毁,弱指针就会变成空的。
想想看,如果 A 持有 B 的 shared_ptr
,而 B 持有 A 的 weak_ptr
,那 A 销毁的时候,B 的 weak_ptr
会自动失效,循环引用就打破了!
我们来改一下上面的例子:
#include <iostream>
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destructor called" << std::endl; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // 使用 weak_ptr
~B() { std::cout << "B destructor called" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
std::cout << "程序结束" << std::endl;
return 0;
}
这次运行,你会发现 A 和 B 的析构函数都被调用了! 循环引用被成功打破。
弱指针的使用方法
弱指针不能直接访问它指向的对象,需要先把它转换成 shared_ptr
才能用。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;
// 尝试访问弱指针指向的对象
if (auto shared_ptr = wp.lock()) { // lock() 返回 shared_ptr
std::cout << "Value: " << *shared_ptr << std::endl;
} else {
std::cout << "Object is no longer alive." << std::endl;
}
sp.reset(); // 销毁 shared_ptr
// 再次尝试访问弱指针指向的对象
if (auto shared_ptr = wp.lock()) {
std::cout << "Value: " << *shared_ptr << std::endl;
} else {
std::cout << "Object is no longer alive." << std::endl;
}
return 0;
}
wp.lock()
方法会尝试把 weak_ptr
转换成 shared_ptr
。如果对象还活着,它会返回一个有效的 shared_ptr
;如果对象已经被销毁,它会返回一个空的 shared_ptr
。所以,在使用之前,一定要检查 lock()
的返回值。
enable_shared_from_this
:优雅地获取自己的 shared_ptr
有时候,我们希望在一个对象内部获取指向自己的 shared_ptr
。 这在事件处理、回调函数等场景中非常有用。但是,直接 std::shared_ptr<MyClass> self(this);
是不安全的,因为这会创建新的 shared_ptr
,导致多个 shared_ptr
指向同一个对象,引用计数混乱,最终导致重复释放。
这时候,enable_shared_from_this
就派上用场了。它是一个基类,可以让你安全地获取指向自己的 shared_ptr
。
#include <iostream>
#include <memory>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
std::shared_ptr<MyClass> get_self() {
return shared_from_this();
}
void do_something() {
std::cout << "Doing something... Address: " << this << std::endl;
}
~MyClass() {
std::cout << "MyClass destructor called. Address: " << this << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
std::shared_ptr<MyClass> self_ptr = obj->get_self();
obj->do_something();
self_ptr->do_something();
std::cout << "程序结束" << std::endl;
return 0;
}
注意几点:
- 继承
enable_shared_from_this
: 让你的类继承这个基类。 - 使用
shared_from_this()
: 在类的成员函数中使用shared_from_this()
方法来获取指向自己的shared_ptr
。这个方法只能在对象已经被shared_ptr
管理之后才能调用,否则会抛出std::bad_weak_ptr
异常。 - 确保对象被
shared_ptr
管理:enable_shared_from_this
的工作原理依赖于对象已经由shared_ptr
管理。因此,不要直接在栈上创建对象并调用shared_from_this()
。
enable_shared_from_this
的原理
enable_shared_from_this
内部维护了一个 weak_ptr
,指向它所关联的对象。当使用 std::make_shared
或其他方式创建一个 shared_ptr
管理的对象时,enable_shared_from_this
会自动将这个 weak_ptr
指向该对象。
shared_from_this()
方法实际上就是调用 weak_ptr
的 lock()
方法,将其转换成 shared_ptr
。 如果对象已经被销毁,lock()
会返回一个空的 shared_ptr
,从而避免了悬空指针。
使用场景总结
使用场景 | 解决方案 |
---|---|
解决循环引用 | 使用 weak_ptr 来打破循环,允许对象被销毁。 |
在对象内部获取指向自己的 shared_ptr |
继承 enable_shared_from_this ,使用 shared_from_this() 方法。 |
事件处理/回调函数 | 使用 enable_shared_from_this 获取 shared_ptr ,避免对象在回调期间被销毁。 |
缓存 | 使用 weak_ptr 实现缓存,当对象不再被使用时,可以自动从缓存中移除。 |
高级用法与注意事项
-
多重继承: 如果你的类已经继承了其他基类,并且还需要使用
enable_shared_from_this
,可以使用虚继承来避免二义性问题:class Base {}; class MyClass : public Base, public std::enable_shared_from_this<MyClass> {};
-
线程安全:
shared_from_this()
本身不是线程安全的。如果在多线程环境下使用,需要进行额外的同步处理。 -
避免裸指针: 尽量避免在代码中使用裸指针,尤其是在涉及到智能指针管理的对象时。使用裸指针容易导致内存泄漏和悬空指针。
-
优先使用
make_shared
:std::make_shared
可以一次性分配对象和控制块,减少内存分配次数,提高性能。 -
shared_ptr
的别名构造:shared_ptr
支持从裸指针构造,并指定析构函数,这使得它可以管理一些非new操作符分配的资源。#include <iostream> #include <memory> void delete_file(FILE* fp) { if (fp) { fclose(fp); std::cout << "File closed." << std::endl; } } int main() { FILE* fp = fopen("test.txt", "w+"); if (fp == nullptr) { perror("Error opening file"); return 1; } std::shared_ptr<FILE> file_ptr(fp, delete_file); // 使用 shared_ptr 管理 FILE* // ... 使用 file_ptr 操作文件 ... fprintf(file_ptr.get(), "Hello, world!"); // 使用 file_ptr.get() 获取裸指针 // 当 file_ptr 离开作用域时,delete_file 会被调用,文件会被关闭 std::cout << "shared_ptr exiting scope." << std::endl; return 0; }
-
自定义删除器:
shared_ptr
的另一个强大的特性是可以自定义删除器。删除器是一个可调用对象,在shared_ptr
的最后一个引用消失时会被调用,用于释放资源。这对于管理非new/delete
分配的资源非常有用,例如文件句柄、互斥锁、数据库连接等。#include <iostream> #include <memory> #include <mutex> class MyResource { public: MyResource(int id) : id_(id) { std::cout << "Resource " << id_ << " acquired." << std::endl; } ~MyResource() { std::cout << "Resource " << id_ << " released." << std::endl; } int id() const { return id_; } private: int id_; }; void delete_my_resource(MyResource* resource) { std::cout << "Custom deleter called for resource " << resource->id() << std::endl; delete resource; } int main() { // 使用自定义删除器创建 shared_ptr std::shared_ptr<MyResource> resource_ptr(new MyResource(123), delete_my_resource); std::cout << "Using the resource..." << std::endl; std::cout << "Resource ID: " << resource_ptr->id() << std::endl; // 当 resource_ptr 离开作用域时,delete_my_resource 会被调用,资源会被释放 std::cout << "shared_ptr exiting scope." << std::endl; return 0; }
总结
弱指针和 enable_shared_from_this
是 C++ 智能指针体系中重要的组成部分。 它们可以帮助我们避免循环引用,安全地获取对象的 shared_ptr
, 从而编写出更加健壮和可靠的代码。 掌握这些技巧,你就可以在 C++ 的江湖里自由驰骋,不再为内存泄漏而烦恼!
好了,今天的讲座就到这里。 感谢各位的观看! 如果觉得有用,别忘了点赞、收藏、转发,一键三连哦! 我们下次再见!