C++用智能指针构建DAG节点父子关系触发SIGSEGV问题
解决C++20 DAG实现中的段错误与析构器警告问题
问题背景
我有Java开发背景,尝试在C20中实现一个有向无环图(DAG),每个节点包含子节点与父节点,且能计算对应值。Java版本代码可正常运行,但C版本出现exit code 139 (interrupted by signal 11: SIGSEGV)段错误,同时收到警告:warning: delete called on 'AbstractDAGNode' that is abstract but has non-virtual destructor。
可正常运行的Java代码
import java.util.ArrayList; import java.util.List; abstract class AbstractDAGNode { List<AbstractDAGNode> children = new ArrayList<>(); List<AbstractDAGNode> parents = new ArrayList<>(); public AbstractDAGNode(List<AbstractDAGNode> children) { this.children = children; for (AbstractDAGNode child : children) { child.parents.add(this); } } public abstract double value(); } class ValueDAGNode extends AbstractDAGNode { double value = 0; public ValueDAGNode(double value) { super(new ArrayList<>()); this.value = value; } @Override public double value() { return this.value; } } class SumDAGNode extends AbstractDAGNode { public SumDAGNode(List<AbstractDAGNode> children) { super(children); } @Override public double value() { double result = 0; for (AbstractDAGNode child : children) { result += child.value(); } return result; } } public class DAG { public static void main(String[] args) { ValueDAGNode v1 = new ValueDAGNode(1); ValueDAGNode v2 = new ValueDAGNode(2); SumDAGNode sum = new SumDAGNode(List.of(v1, v2)); System.out.println(sum.value()); } }
出现错误的C++代码
class AbstractDAGNode { protected: std::vector<std::shared_ptr<AbstractDAGNode>> children{}; std::vector<std::shared_ptr<AbstractDAGNode>> parents{}; public: explicit AbstractDAGNode(std::vector<std::shared_ptr<AbstractDAGNode>> children) { this->children = std::move(children); for (auto &child: this->children) { child->parents.emplace_back(this); } } virtual double Value() = 0; }; class ValueDAGNode : public AbstractDAGNode { protected: double value{}; public: explicit ValueDAGNode(double value) : AbstractDAGNode({}), value(value) {} double Value() override { return value; } }; class SumDAGNode: public AbstractDAGNode { public: explicit SumDAGNode(const std::vector<std::shared_ptr<AbstractDAGNode>> &children) : AbstractDAGNode(children) {} double Value() override { double result = 0; for (auto &child: children) { result += child->Value(); } return result; } }; TEST_CASE("dag1") { std::shared_ptr<ValueDAGNode> v1 = std::make_shared<ValueDAGNode>(1); std::shared_ptr<ValueDAGNode> v2 = std::make_shared<ValueDAGNode>(2); std::vector<std::shared_ptr<AbstractDAGNode>> children = std::vector<std::shared_ptr<AbstractDAGNode>>{v1, v2}; std::shared_ptr<SumDAGNode> sum_node = std::make_shared<SumDAGNode>(children); }
错误原因分析
1. 段错误的根源
问题出在构造函数的这段代码:
for (auto &child: this->children) { child->parents.emplace_back(this); }
- 直接将原始指针
this存入std::shared_ptr<AbstractDAGNode>容器parents,会创建一个独立的shared_ptr实例,该实例会认为自己是当前对象的唯一所有者。但当前对象已经被std::make_shared创建的另一个shared_ptr管理,这会导致同一个对象被多个独立的shared_ptr引用,析构时重复释放内存,触发段错误。 - 父节点用
shared_ptr持有子节点,子节点用shared_ptr持有父节点,形成循环引用,内存无法正常释放。
2. 析构器警告的原因
抽象基类AbstractDAGNode没有声明虚析构函数,当通过基类指针销毁派生类对象时,会触发未定义行为,编译器因此发出警告。
修复方案
- 让
AbstractDAGNode继承std::enable_shared_from_this<AbstractDAGNode>,通过shared_from_this()获取当前对象的合法shared_ptr。 - 将
parents的类型改为std::vector<std::weak_ptr<AbstractDAGNode>>,避免循环引用,weak_ptr不会增加引用计数,不影响对象生命周期。 - 给
AbstractDAGNode添加虚析构函数,确保派生类对象能被正确销毁。 - 修改构造函数中添加父节点的逻辑,使用
shared_from_this()转换为weak_ptr存入子节点的parents列表。
修复后的完整C++代码
#include <vector> #include <memory> // 引入测试框架头文件,比如Catch2 // #include <catch2/catch_test_macros.hpp> class AbstractDAGNode : public std::enable_shared_from_this<AbstractDAGNode> { protected: std::vector<std::shared_ptr<AbstractDAGNode>> children{}; std::vector<std::weak_ptr<AbstractDAGNode>> parents{}; public: explicit AbstractDAGNode(std::vector<std::shared_ptr<AbstractDAGNode>> children) { this->children = std::move(children); for (auto &child : this->children) { // 将当前节点的shared_ptr转换为weak_ptr存入子节点的parents child->parents.emplace_back(shared_from_this()); } } // 添加虚析构函数 virtual ~AbstractDAGNode() = default; virtual double Value() = 0; }; class ValueDAGNode : public AbstractDAGNode { protected: double value{}; public: explicit ValueDAGNode(double value) : AbstractDAGNode({}), value(value) {} double Value() override { return value; } }; class SumDAGNode : public AbstractDAGNode { public: explicit SumDAGNode(const std::vector<std::shared_ptr<AbstractDAGNode>> &children) : AbstractDAGNode(children) {} double Value() override { double result = 0; for (auto &child : children) { result += child->Value(); } return result; } }; TEST_CASE("dag1") { std::shared_ptr<ValueDAGNode> v1 = std::make_shared<ValueDAGNode>(1); std::shared_ptr<ValueDAGNode> v2 = std::make_shared<ValueDAGNode>(2); std::vector<std::shared_ptr<AbstractDAGNode>> children = {v1, v2}; std::shared_ptr<SumDAGNode> sum_node = std::make_shared<SumDAGNode>(children); // 验证计算结果 REQUIRE(sum_node->Value() == 3.0); }
关键修复点说明
std::enable_shared_from_this:允许对象在自身成员函数中获取指向自己的shared_ptr,避免手动创建独立shared_ptr导致的重复析构问题。std::weak_ptr:用于父节点引用,不增加对象引用计数,解决循环引用问题,需要时可通过lock()方法获取有效shared_ptr(判断对象是否存在)。- 虚析构函数:确保基类指针指向派生类对象时,析构函数能正确调用派生类的析构逻辑,避免未定义行为。
内容的提问来源于stack exchange,提问作者John Doe
相关产品推荐
相关产品推荐

