You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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没有声明虚析构函数,当通过基类指针销毁派生类对象时,会触发未定义行为,编译器因此发出警告。

修复方案

  1. 让AbstractDAGNode继承std::enable_shared_from_this<AbstractDAGNode>,通过shared_from_this()获取当前对象的合法shared_ptr。
  2. 将parents的类型改为std::vector<std::weak_ptr<AbstractDAGNode>>,避免循环引用,weak_ptr不会增加引用计数,不影响对象生命周期。
  3. 给AbstractDAGNode添加虚析构函数,确保派生类对象能被正确销毁。
  4. 修改构造函数中添加父节点的逻辑,使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 12:35:16