C++ 11 个委托构造函数 纯虚拟方法和函数调用 -- 危险?

C++ 11 Delegated Constructor Pure Virtual Method & Function Calls -- Dangers?

本文关键字:方法 函数调用 危险 虚拟 构造函数 C++      更新时间:2023-10-16

不是从构造函数调用虚拟函数和纯虚拟函数的重复:

前一个问题与C++03有关,而不是C++11中新的构造函数委派行为,该问题没有解决通过使用委派来缓解未定义行为的问题,以确保在执行纯虚拟实现之前进行正确的构建

在C++11中,在类的构造函数中,在构造过程中,但在通过构造函数委托"完全构造"了类/对象之后,调用纯虚拟函数有什么危险?

显然,在C++11规范的某个地方存在这样的约束,

会员功能(包括虚拟会员功能,10.3)可以要求建造一个物体。类似地,下面的对象构造可以是typeid运算符的操作数。。-12.6.2[C++工作草案]第13条(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf)找不到已发布规范的"合理使用"版本。

C++11考虑在任何构造函数完成后构造的对象处决由于将允许执行多个构造函数,这意味着每个委托构造函数都将在完全构造的自己类型的对象。派生类构造函数将在其基类中的所有委派完成后执行。-维基百科说这是一个C++11的东西。

实际的C++11引用未知。

以下示例在Visual Studio 2012 C++编译器的11月CTP中编译和运行:

#include <string>
/**************************************/
class Base
{
public:
    int sum;
    virtual int Do() = 0;
    void Initialize()
    {
        Do();
    }
    Base()
    {
    }
};
/**************************************/
// Optionally declare class as "final" to avoid
// issues with further sub-derivations.
class Derived final : public Base
{
public:
    virtual int Do() override final
    {
        sum = 0 ? 1 : sum;
        return sum / 2 ; // .5 if not already set.
    }
    Derived(const std::string & test)
        : Derived() // Ensure "this" object is constructed.
    {
        Initialize(); // Call Pure Virtual Method.
    }
    Derived()
        : Base()
    {
        // Effectively Instantiating the Base Class.
        // Then Instantiating This.
        // The the target constructor completes.
    }
};


/********************************************************************/
int main(int args, char* argv[])
{
    Derived d;
    return 0;
}

随着更新,示例代码对我来说还可以,但需要注意的是,如果您创建了Derived的子类,则Derived(conststd::string&)不会调用该子类对Do()的覆盖,相反,Derived::Do(;)仍然会被调用;这可能不是你想要的。特别是,当从Derived(const std::string&)构造函数调用Initialize()时,该对象仍然"只是"一个Derived对象,而不是SubDerived对象(因为构造代码的SubDerived层尚未启动),这就是为什么要调用Derived::Do()而不是SubDerive::Do)。

Q: 如果子类使用相同的委托模式来确保所有东西都以相同的方式实例化,该怎么办?

A: 这基本上是可行的,但前提是可以在调用SubDerive::Do()之前调用Derived::Do)。

特别是,假设您有一个类SubDerived,它做了与上面Derived相同的事情。然后当调用代码这样做时:

SubDerived foo("Hello");

将出现以下调用序列:

Base()
Derived()
Derived(const std::string &)
  Base::Initialize()
    Derived::Do()
SubDerived()
SubDerived(const std::string &)
  Base::Initialize()
    SubDerived::Do()

因此,是的,SubDerived::Do()最终会被调用,但Derived:::Do()也会被调用。这是否会成为一个问题取决于各种Do()方法的实际作用

一些建议:从构造函数中调用虚拟方法通常不是最好的方法。您可能需要考虑简单地要求调用代码在构建对象后手动调用对象上的Do()。调用代码需要做更多的工作,但优点是可以避免在对部分构建的对象进行虚拟方法调用时出现的不太明显或不方便的语义。

在典型的单构造函数继承场景中,UB调用基构造函数中的纯虚拟函数:

[C++11: 10.4/6]:成员函数可以从抽象类的构造函数(或析构函数)调用;从这样的构造函数(或析构函数)直接或间接地为正在创建(或销毁)的对象对纯虚拟函数进行虚拟调用(10.3)的效果是未定义的。

struct Base
{
   Base()
   {
      foo();  // UB
   }
   virtual void foo() = 0;
};
struct Derived : Base
{
   virtual void foo() {}
};

对于在委托构造函数调用中进行的此类调用,这里没有任何豁免,因为在这一点上,对象的派生程度更高的部分仍然没有被构造。

struct Base
{
   Base()
   {
      foo();  // still UB
   }
   Base(int) : Base() {};
   virtual void foo() = 0;
};
struct Derived : Base
{
   virtual void foo() {}
};

这是你引用的维基百科文章:

C++11考虑在任何构造函数完成执行后构造的对象由于将允许执行多个构造函数,这意味着每个委托构造函数将在其自己类型的完全构造的对象上执行。派生类构造函数将在其基类中的所有委托完成后执行

关键是第二个粗体的句子,而不是第一个,因为人们可能会一眼就误解。

但是,您发布的代码片段很好,这是因为派生的构造函数主体正在执行,而不是已经完全构造好的抽象类的构造函数。也就是说,你必须要求证明它是安全的,这应该表明这不是最具表现力或直观的方法,我会在你的设计中尽量避免它。