继承含std::enable_shared_from_this的类后触发std::bad_weak_ptr异常原因问询
问题重现
代码示例
#include <memory> class A: public std::enable_shared_from_this<A> {}; struct B: public A, public std::enable_shared_from_this<B> { std::shared_ptr<B> get() { return enable_shared_from_this<B>::shared_from_this(); } }; int main(){ auto b = std::make_shared<B>(); auto p = b->get(); }
编译与运行环境
- 编译器:Clang 15.0.0 / GCC 12.2
- 编译参数:
-std=c++20 -O2 -Wall -fsanitize=address
报错信息
terminate called after throwing an instance of 'std::bad_weak_ptr' what(): bad_weak_ptr AddressSanitizer:DEADLYSIGNAL ================================================================= ==1==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x7f0e60632941 bp 0x7f0e607fd780 sp 0x7ffdb646eee0 T0) ==1==The signal is caused by a READ memory access. ==1==Hint: this fault was caused by a dereference of a high value address (see register values below). Disassemble the provided pc to learn which register was used. #0 0x7f0e60632941 in abort (/lib/x86_64-linux-gnu/libc.so.6+0x22941) #1 0x7f0e60a1ba48 (/opt/compiler-explorer/gcc-12.2.0/lib64/libstdc++.so.6+0xa9a48) #2 0x7f0e60a27079 (/opt/compiler-explorer/gcc-12.2.0/lib64/libstdc++.so.6+0xb5079) #3 0x7f0e60a270e4 in std::terminate() (/opt/compiler-explorer/gcc-12.2.0/lib64/libstdc++.so.6+0xb50e4) #4 0x7f0e60a27336 in __cxa_throw (/opt/compiler-explorer/gcc-12.2.0/lib64/libstdc++.so.6+0xb5336) #5 0x4011a3 in std::__throw_bad_weak_ptr() /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/shared_ptr_base.h:96 #6 0x4011a3 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count(std::__weak_count<(__gnu_cxx::_Lock_policy)2> const&) /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/shared_ptr_base.h:1245 #7 0x4011a3 in std::__shared_ptr<B, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<B, void>(std::__weak_ptr<B, (__gnu_cxx::_Lock_policy)2> const&) /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/shared_ptr_base.h:1548 #8 0x4011a3 in std::shared_ptr<B>::shared_ptr<B, void>(std::weak_ptr<B> const&) /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/shared_ptr.h:381 #9 0x4011a3 in std::enable_shared_from_this<B>::shared_from_this() /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/shared_ptr.h:935 #10 0x4011a3 in B::get() /app/example.cpp:7 #11 0x4011a3 in main /app/example.cpp:13 #12 0x7f0e60634082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) #13 0x4016cd in _start (/app/output.s+0x4016cd) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV (/lib/x86_64-linux-gnu/libc.so.6+0x22941) in abort ==1==ABORTING
异常原因
核心问题在于类B同时继承了两个不同特化版本的std::enable_shared_from_this:
- 通过基类
A间接继承std::enable_shared_from_this<A> - 直接继承
std::enable_shared_from_this<B>
std::enable_shared_from_this的工作逻辑是:当用std::make_shared(或对象赋值给std::shared_ptr)创建实例时,shared_ptr会自动初始化enable_shared_from_this内部的weak_ptr成员,后续shared_from_this()就是通过这个weak_ptr生成新的shared_ptr。
但C++标准明确规定,一个类不能直接或间接继承多个std::enable_shared_from_this的不同特化版本,这种场景属于未定义行为。在你的代码中,std::make_shared<B>只能初始化其中一个enable_shared_from_this的weak_ptr(此处为A基类中的enable_shared_from_this<A>成员),而B直接继承的enable_shared_from_this<B>内部的weak_ptr完全未被初始化。调用enable_shared_from_this<B>::shared_from_this()时,代码尝试从未初始化的weak_ptr构造shared_ptr,最终抛出std::bad_weak_ptr异常。
解决方法
需要确保整个继承链中只有一个std::enable_shared_from_this特化,常见方案有两种:
方案1:移除基类的enable_shared_from_this
如果A不需要单独使用shared_from_this(),可移除其enable_shared_from_this<A>继承,仅让最派生类B继承enable_shared_from_this<B>:
#include <memory> class A {}; struct B: public A, public std::enable_shared_from_this<B> { std::shared_ptr<B> get() { return shared_from_this(); } }; int main(){ auto b = std::make_shared<B>(); auto p = b->get(); }
方案2:通过基类的shared_from_this()转换类型
如果A必须保留enable_shared_from_this<A>,则B无需再继承enable_shared_from_this<B>,而是通过static_pointer_cast将A的shared_ptr转换为B的shared_ptr:
#include <memory> class A: public std::enable_shared_from_this<A> {}; struct B: public A { std::shared_ptr<B> get() { return std::static_pointer_cast<B>(shared_from_this()); } }; int main(){ auto b = std::make_shared<B>(); auto p = b->get(); }
内容的提问来源于stack exchange,提问作者Rish

