【问题标题】:Is the destruction of the objects properly happend?对象的破坏是否正确发生?
【发布时间】:2020-01-18 05:32:40
【问题描述】:

我在这里读过这篇文章:When to use virtual destructors? 我的想法是,每当我们使用new 或智能指针动态创建对象时,基类应该有一个适当的virtual 析构函数,用于在删除时销毁对象。

然后我发现了一些类似下面的代码(简化形式),它错过了Base中的virtual析构函数:

class Base
{
public:
    // some static members
};

class Derived1 final : public Base
{
public:
    // other members
    // default constructor does not construct the `Base` in constructor member intilizer
    Derived1() {};
    virtual ~Derived1() = default;
};
class Derived2 final : public Base
{
public:
    // other members
    Derived2() {}; // default constructor does not construct the `Base`
    ~Derived2() = default;
};

int main()
{
    // creating Derived1 dynamically        // 1
    Derived1 *d1Object = new Derived1{};

    // creating Derived2 dynamically        // 2
    Derived2 *d2Object1 = new Derived2{};

    // creating Derived2 statically         // 3
    Derived2 d2Object2{};

    // clean up
    delete d1Object;
    delete d2Object1;
}

我的问题是:

  • 在任何情况下我是否有未定义的行为(1, 2, 3)?为什么?
  • 在两个派生类的构造函数的成员初始化器列表中构造Base 不是必需的(在上述特定情况下)?

我正在使用 C++11。

【问题讨论】:

  • 使用指向具有非虚拟析构函数的基类的指针删除派生类对象会导致未定义的行为。由于您的指针不是指向具有非虚拟析构函数的基类,因此我认为您在这里避免了 UB。
  • 除了已经给出的答案之外,您不需要将Derived1 的析构函数声明为virtual,因为Derived1final(因此永远不会被继承)。

标签: c++ class c++11 polymorphism virtual


【解决方案1】:

该代码中的对象指针实际上并不是多态的*d1Object 的静态类型与其动态类型相同,即Derived1&(其他对象也是如此) )。

因此,直接销毁它会调用正确的析构函数~Derived1。因此一切都很好。以下代码出现问题:

Base* b = new Derived1();
delete b;

这隐式调用b->~Base();。由于~Base 不是虚拟的,~Derived1 不会没有被调用,因此你会得到 UB。

std::unique_ptr<Base> 也是如此。但是,对于std::shared_ptr<Base>,它不是正确的,因为shared_ptr<> 构造函数是模板化的,并且存储了构造它的实际对象的析构函数。也就是说,以下内容很好,并且为两个对象调用了正确的析构函数:

std::shared_ptr<Base> p1{new Derived1{});
std::shared_ptr<Base> p2 = std::make_shared<Derived1>();

关于构造函数的问题,与数据成员的问题相同:即使初始化列表中缺少它们,它们仍然会按照声明的顺序进行默认初始化,对于基类,在从最远的父母到最近的父母的顺序。换句话说,你不能初始化一个父类,它总是会发生。

【讨论】:

  • 当所有涉及的构造函数实际上什么都不做时,真的是 UB 吗?
  • @foreknownas_463035818 “什么都不做”和“默认”是两种截然不同的说法。 OP 的代码包含一条注释,说“其他成员”。如果其中任何一个具有非平凡的析构函数,那么Derived1 的默认析构函数也是非平凡的,因此不调用它的是UB。 — 然而,我相信即使对于默认析构函数,这在技术上也可能是 UB,但我懒得去查:我只是不写这样的代码。
  • 我指的是发布的 OP 代码(即任何地方都没有成员)。反正主要是学术兴趣,我也不会写那样的代码。
  • @foreknownas_463035818 是的,对于编写的代码,我不得不承认我不知道答案,但正如所指出的,我相信它可能仍然是 UB。
  • 即使shared_ptr 也无法施展魔法。如果你这样做 Base* b = new Derived1{}; std::shared_ptr&lt;Base&gt; p1(b); 它有同样的问题。尽管如此,对每个实例删除器的观察还是不错的。
【解决方案2】:

问题 1:在任何情况下(1、2、3)我是否有未定义的行为?为什么?

提供的代码示例中没有未定义的行为。 如果您尝试持有指向Base 的指针并通过该指针删除,则会出现未定义的行为。在提供的示例中,您知道要删除的具体类

问题 2:在两个派生类的构造函数的成员初始值设定项列表中构造 Base 是否不是必需的(在上述特定情况下)?

基类构造函数总是被调用,无论你是否显式调用它。如果没有显式调用,则将调用默认构造函数。如果没有默认的无参数构造函数,并且您没有调用特定的构造函数,则代码将无法编译。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-26
    • 2018-04-12
    • 2013-04-22
    • 1970-01-01
    • 2021-10-04
    • 2015-02-14
    • 2016-04-03
    相关资源
    最近更新 更多