【问题标题】:Multiple inheritance and unique_ptr destruction多重继承和 unique_ptr 破坏
【发布时间】:2016-12-31 15:27:02
【问题描述】:

我有经典的(可能有问题的)多重继承菱形方案。

  • B 继承 A
  • C 继承 A
  • D 继承 C 和 B

我想要一个std::vector,它可以包含CD 对象,所以我将其设为std::vector<C>,这是D 的爸爸和它 工作正常。

但是当我使用:std::vector<std::unique_ptr<C>> 时,我会在向量被破坏时出现分段错误。

** glibc detected *** ./a.out: free(): invalid pointer: 0x0000000009948018***

为什么会有差异?对我来说,即使是第一个实现也是有问题的。

代码

#include <string>
#include <vector>
#include <memory>

class A
{
public:
    A() = default;
};

class B : public virtual A
{
public:
    B() = default;
};

class C : public virtual A
{
public:
    C() = default;
};

class D : public B, public C
{
public:
    D() = default;
};


int main()
{
    { // this crashes
    std::vector<std::unique_ptr<C>> v;
    std::unique_ptr<D> s1(new D());
    v.push_back(std::move(s1));
    std::unique_ptr<C> s2(new C());
    v.push_back(std::move(s2));
    }

    { // this is fine
    std::vector<C> v;
    D s1;
    v.push_back(s1);
    C s2;
    v.push_back(s2);
    }

    return 0;
};

【问题讨论】:

  • "我想要一个std::vector,它可以包含CD 对象,所以我将其设为std::vector&lt;C&gt;" 乍一看这似乎可行,但我不知道'认为它不会按您期望的方式工作。

标签: c++ c++11 inheritance vector unique-ptr


【解决方案1】:

你应该将你的析构函数声明为虚拟的。否则,如果使用指向 C 的指针删除了您的类 D,则将调用 ~C() 析构函数,并且将丢失清理的重要部分。

还请注意,在您的第二部分(不使用unique_ptr)中,您进行了一些对象切片。这意味着您正在将D 类型的s1 的副本创建到C 类的新对象中,因此您可能会丢失特定于D 的额外信息。

这是更正后的代码:

#include <string>
#include <vector>
#include <memory>

class A
{
public:
    A() = default;
    virtual ~A() {};
};

class B : public virtual A
{
public:
    B() = default;
    virtual ~B() {};
};

class C : public virtual A
{
public:
    C() = default;
    virtual ~C() {};
};

class D : public B, public C
{
public:
    D() = default;
    virtual ~D() {};
};


int main()
{
    { // this does not crashe anymore
        std::vector<std::unique_ptr<C>> v;
        std::unique_ptr<D> s1(new D());
        v.push_back(std::move(s1));
        std::unique_ptr<C> s2(new C());
        v.push_back(std::move(s2));
    }

    { // this is fine because you slice D into C, still that fine ?
        std::vector<C> v;
        D s1;
        v.push_back(s1);
        C s2;
        v.push_back(s2);
    }

    return 0;
}

另见


注意

如 cmets 中所述,如果将 A 析构函数标记为虚拟,则每个派生类也将具有虚拟析构函数。 到处写它可以使它更明确。这是风格问题。

【讨论】:

  • 谢谢@dkg。我需要向所有类声明它吗?只有当我为CD 这样做时,它似乎才有效。但是为什么我不使用unique_ptr时这不是问题?
  • 这很好,因为当你调用 push_back 方法时你正在做一个复制,因此你的向量将只产生 C 类,你的 D 类型的 s1 将在你的小堆栈的末尾被破坏。
  • 如果您将~C() 设为虚拟,那么~D() 也将是虚拟的(但我发现明确地说出来会有所帮助)。然后通过指向C 的指针进行析构将调用虚拟析构函数。如果删除指向A 的指针,则将调用A 的(非虚拟)析构函数。这有帮助吗?
  • @DimitrisDakopoulos 请参阅我的第二部分更新。您认为这很好,但是您可以添加一个打印类名称的方法并在您的向量上调用它以查看对象切片的行为,所有内容都将打印您的 C 类中定义的内容,D 类中的信息将是迷路了。
  • 标准建议是“在任何打算用作多态基类的类中声明一个虚拟析构函数”。一个好的经验法则(由gcc -Weffc++ 实现)是任何具有虚方法的类都应该有一个虚析构函数。
【解决方案2】:

您的“这很好”示例进行切片,向量仅包含 C 的实例,这就是它“有效”但没有达到您预期的原因。正如 dkg 指出的那样,解决方案是使用虚拟 dtors。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多