通过std::ref向std::bind传递shared_ptr会丢失多态性?
std::ref与std::bind结合导致dynamic_cast触发SIGSEGV崩溃的原因分析
问题描述
我在尝试将Base类的this指针通过dynamic_cast转换为Derived类指针时触发了SIGSEGV崩溃,怀疑该问题与std::bind和std::ref的使用有关。当注释掉使用std::ref的版本、改用无引用版本时,程序可正常运行无崩溃。疑惑为何std::ref会导致这种情况,难道是Base类shared_ptr的多态性被破坏了?
可复现代码
头文件 h.hpp
#include <functional> #include <iostream> #include <map> #include <memory> #include <vector> using namespace std; template <typename T> class Derived; class Base { public: virtual ~Base() {} template <typename T> void foo(const T &t) { cout << "Base foo\n"; auto derived = dynamic_cast<Derived<T> *>(this); // SIGSEGV crash! derived->foo(t); } private: }; template <typename T> class Derived : public Base { public: void foo(const T &t) { cout << "Derived foo\n"; } private: }; class Outter { public: void init(); void run(); private: void run_foo(shared_ptr<Base> &base); class Inner { public: void run() { for (const auto &f : foos_) { f(); } } void set_foo(function<void()> f) { foos_.push_back(f); } private: static vector<function<void()>> foos_; }; Inner inner_; vector<shared_ptr<Base>> bases_; };
源文件 main.cpp
#include "h.hpp" vector<function<void()>> Outter::Inner::foos_; void Outter::init() { shared_ptr<Base> base = make_shared<Derived<int>>(); bases_.push_back(base); // inner_.set_foo(bind(&Outter::run_foo, this, base)); // no ref version inner_.set_foo(bind(&Outter::run_foo, this, ref(base))); // ref verison } void Outter::run() { inner_.run(); } void Outter::run_foo(shared_ptr<Base> &base) { int t = 123; base->foo(t); } int main() { Outter outter; outter.init(); outter.run(); return 0; }
原因分析与解决方案
这根本不是多态性被破坏的问题,而是引用了已经销毁的局部变量导致的未定义行为:
- 在
Outter::init()函数中,shared_ptr<Base> base是一个局部变量,当init()执行完毕后,这个局部变量的生命周期就结束了。 - 当你用
std::ref(base)配合std::bind时,bind存储的是这个局部shared_ptr的引用,而不是拷贝。当后续inner_.run()调用绑定的函数时,引用指向的局部变量已经被销毁,此时base引用的是一个无效的内存地址。 - 后续对
base->foo(t)的调用本质是操作一个已经失效的shared_ptr,这会导致未定义行为,包括SIGSEGV崩溃,此时dynamic_cast的崩溃只是这种未定义行为的表现之一。
而无引用版本中,std::bind会拷贝shared_ptr<Base> base,这个拷贝的shared_ptr会和bases_中的元素共享同一个对象的所有权,保证了对象的生命周期足够长,因此程序可以正常运行。
修复方案
- 去掉
std::ref,让bind拷贝shared_ptr(即测试过的正常版本); - 如果一定要传递引用,需要确保被引用的
shared_ptr生命周期长于绑定函数的调用时机,比如绑定bases_中元素的引用(ref(bases_.back())),因为bases_是Outter的成员变量,生命周期和Outter实例一致。
内容的提问来源于stack exchange,提问作者Komgcn
相关产品推荐
相关产品推荐

