C++ 物品的销毁是否正确?

C++ 物品的销毁是否正确?,c++,class,c++11,polymorphism,virtual,C++,Class,C++11,Polymorphism,Virtual,我在这里读过这篇文章: 我的想法是,每当我们使用新指针或智能指针动态创建对象时,基类应该有一个适当的虚拟析构函数,用于在删除时销毁对象 然后,我发现了一些类似于以下简化形式的代码,它们缺少了基础中的虚拟析构函数: 我的问题是: 在任何情况下,我是否有未定义的行为1、2、3?为什么? 在上述特定情况下,在两个派生类的构造函数的成员初始值设定项列表中,构造基不是必不可少的吗? 我使用的是C++11。该代码中的对象指针实际上不是多态的:*d1Object的静态类型与其动态类型相同,即Derived1&

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

然后,我发现了一些类似于以下简化形式的代码,它们缺少了基础中的虚拟析构函数:

我的问题是:

在任何情况下,我是否有未定义的行为1、2、3?为什么? 在上述特定情况下,在两个派生类的构造函数的成员初始值设定项列表中,构造基不是必不可少的吗?
我使用的是C++11。

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

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

Base* b = new Derived1();
delete b;
这会隐式调用b->~Base;。由于~Base不是虚拟的,~Derived1不会被调用,因此您会得到UB

std::unique\u ptr也是如此。但是,对于std::shared_ptr,情况并非如此,因为shared_ptr构造函数是模板化的,并存储它所构造的实际对象的析构函数。也就是说,以下操作很好,可以为两个对象调用正确的析构函数:

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

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

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

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

Base* b = new Derived1();
delete b;
这会隐式调用b->~Base;。由于~Base不是虚拟的,~Derived1不会被调用,因此您会得到UB

std::unique\u ptr也是如此。但是,对于std::shared_ptr,情况并非如此,因为shared_ptr构造函数是模板化的,并存储它所构造的实际对象的析构函数。也就是说,以下操作很好,可以为两个对象调用正确的析构函数:

std::shared_ptr<Base> p1{new Derived1{});
std::shared_ptr<Base> p2 = std::make_shared<Derived1>();
至于您关于构造函数的问题,与数据成员的问题相同:即使它们从初始化器列表中丢失,它们仍然按照声明的顺序进行默认初始化,对于基类,则按照从最远到最近父级的顺序进行默认初始化。换句话说,你不能初始化父类,它总是发生的。

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

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

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

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

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

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

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


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

使用指向具有非虚拟析构函数的基类的指针删除派生类对象会导致未定义的行为。由于您的指针不是指向具有非虚拟析构函数的基类,我认为您在这里避免了UB。除了已经给出的答案之外,您不需要将Derived1的析构函数声明为虚拟的,因为Derived1是最终的,因此永远不会被继承。使用指向的指针删除派生类对象
具有非虚拟析构函数的基类会导致未定义的行为。由于您的指针不是指向具有非虚拟析构函数的基类,因此我认为您在这里避免了UB。除了已经给出的答案之外,您不需要将Derived1的析构函数声明为虚拟的,因为Derived1是最终的,因此永远不会被继承。当所有涉及的构造函数实际上什么都不做时,它真的是UB吗?@formerlyknownas463035818“什么都不做”和“默认”是两个非常不同的语句。OP的代码包含一条注释,上面写着“其他成员”。如果其中任何一个具有非平凡的析构函数,那么Derived1的默认析构函数也是非平凡的,因此不调用它的是UB。-然而,我相信,即使对于默认的析构函数,从技术上来说,这也可能是UB,但我太懒了,不想去查它:我只是不写这样的代码。我指的是发布的OPs代码,即没有任何成员。不管怎么说,这主要是出于学术兴趣,我也不会写这样的代码。@formerlyknownas_463035818是的,对于编写的代码,我不得不承认我不知道答案,如前所述,我相信它可能仍然是UB。即使是共享的ptr也无法发挥神奇的作用。如果您使用Base*b=new-Derived1{};std::共享的ptr p1b;它也有同样的问题。不过,对每个实例的deleter进行了很好的观察。当所有涉及的构造函数实际上什么都不做时,它真的也是UB吗?@formerlyknownas_463035818“donothing”和“defaulted”是两个非常不同的语句。OP的代码包含一条注释,上面写着“其他成员”。如果其中任何一个具有非平凡的析构函数,那么Derived1的默认析构函数也是非平凡的,因此不调用它的是UB。-然而,我相信,即使对于默认的析构函数,从技术上来说,这也可能是UB,但我太懒了,不想去查它:我只是不写这样的代码。我指的是发布的OPs代码,即没有任何成员。不管怎么说,这主要是出于学术兴趣,我也不会写这样的代码。@formerlyknownas_463035818是的,对于编写的代码,我不得不承认我不知道答案,如前所述,我相信它可能仍然是UB。即使是共享的ptr也无法发挥神奇的作用。如果您使用Base*b=new-Derived1{};std::共享的ptr p1b;它也有同样的问题。不过,对每实例删除器的观察还是不错的。