多继承下父类已继承std::enable_shared_from_this时子类的用法安全性
问题描述
假设类D多继承自A、B、C三个类,需要能够调用
shared_from_this()获取自身的std::shared_ptr<D>。若其中父类B已继承std::enable_shared_from_this<B>,仅通过std::static_pointer_cast转换是否能安全为D提供shared_from_this功能?
示例代码:
#include <iostream> #include <memory> #include <vector> #include <cassert> class A { public: void printX() { std::cout << x << std::endl; } protected: A(int x) : x{ x } {} private: int x{ 3 }; }; class B : public std::enable_shared_from_this<B> { public: void printY() { std::cout << y << std::endl; } protected: B(double y) : y{ y } {} private: double y{ 5.89 }; }; class C { public: void printS() { std::cout << s << std::endl; } protected: C(const std::string& s) : s{ s } {} private: std::string s{ "just a test" }; }; class D : public A, public B, public C { public: static std::shared_ptr<D> create(std::size_t n) { std::shared_ptr<D> dptr{ new D(1, 3.14, "parent node") }; for (std::size_t i = 0; i < n; ++i) dptr->addChild(std::shared_ptr<D>{ new D(2, 7.7, "child node") }); return dptr; } ~D() { std::cout << "~D()" << std::endl; } std::shared_ptr<D> getChild(std::size_t index) const { return children.at(index); } std::shared_ptr<D> getParent() const { return parent.lock(); } protected: D(int x, double y, const std::string& s) : A{ x }, B{ y }, C{ s } { } void addChild(const std::shared_ptr<D>& child) { child->parent = std::static_pointer_cast<D>(shared_from_this()); children.push_back(child); } private: std::weak_ptr<D> parent; std::vector<std::shared_ptr<D>> children; }; int main() { auto dptr = D::create(3); auto child1 = dptr->getChild(1); auto parent = child1->getParent(); assert(parent == dptr); parent->printX(); parent->printY(); parent->printS(); }
分析与结论
当前写法的安全性
你测试的这段代码是安全的,原因如下:
当D对象被std::shared_ptr<D>托管时,B继承的enable_shared_from_this<B>内部的weak_ptr会被正确初始化——shared_ptr构造时会自动检测继承链中的enable_shared_from_this实例并完成关联。此时调用shared_from_this()得到的std::shared_ptr<B>指向D对象的B子对象,通过std::static_pointer_cast<D>转换是类型安全的,因为D是B的公有派生类,转换后的指针能正确指向完整的D对象。
潜在陷阱
虽然当前代码能正常运行,但存在几个需要注意的隐患:
- 非公有继承限制:如果后续修改
D继承B的方式为私有或保护,std::static_pointer_cast会编译失败,因为派生类到基类的转换路径不可访问。 - 重复继承
enable_shared_from_this风险:如果后续给D也添加enable_shared_from_this<D>继承,或者其他父类也引入enable_shared_from_this,会导致多个enable_shared_from_this实例共存,此时shared_from_this()调用会产生歧义,甚至导致内部weak_ptr关联错误,引发悬空指针或重复析构。 - 裸指针构造
shared_ptr的危险:如果用同一个D对象的裸指针分别构造std::shared_ptr<D>和std::shared_ptr<B>,enable_shared_from_this<B>的weak_ptr只会关联其中一个控制块,转换得到的shared_ptr<D>会和原shared_ptr<D>属于不同的引用计数体系,最终导致对象被重复析构。
更健壮的替代方案
推荐直接让D继承std::enable_shared_from_this<D>,即使B已经继承了enable_shared_from_this<B>。C++标准允许这种写法,因为每个enable_shared_from_this是独立的模板实例,不会产生二义性。此时D可以直接调用shared_from_this()获取std::shared_ptr<D>,无需类型转换,代码更清晰,也能避免上述所有陷阱。
内容的提问来源于stack exchange,提问作者mentalmushroom

