e.s*_*hen 14 c++ constructor delegation pure-virtual c++11
不是从构造函数调用虚函数和纯虚函数的重复:
前一个问题涉及C++ 03,而不是C++ 11中新的构造函数委派行为,并且该问题没有通过使用委托来确保在执行纯虚拟实现之前正确构造来解决未定义行为的问题.
在C++ 11中,在构造期间在类的构造函数中调用Pure Virtual函数有什么危险,但是在通过构造函数委托"完全构造"类/对象之后?
显然,在C++ 11规范的某个地方存在这样的约束,
可以为正在构造的对象调用成员函数(包括虚拟成员函数,10.3).类似地,正在构造的对象可以是typeid运算符的操作数. - [C++工作草案]的12.6.2#13(http://www.open-std.org/jtc1/sc22/wg21/docs/ papers/2011/n3242.pdf)找不到已发布规范的"合理使用"版本.
一旦任何构造函数完成执行,C++ 11就会考虑构造一个对象.由于将允许多个构造函数执行,这意味着每个委托构造函数将在其自己类型的完全构造的对象上执行.派生类构造函数将在其基类中的所有委托完成后执行.- 维基百科说这是C++ 11的事情.
实际C++ 11参考未知.
以下示例在Visual Studio 2012 C++编译器的Nov CTP中编译和运行RUNS:
#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;
}
Run Code Online (Sandbox Code Playgroud)
通过更新,示例代码看起来没问题,但需要注意的是,如果您创建了Derived的子类,子类的Do()的重写将不会被Derived(const std :: string&)调用,而是派生:: Do()仍会被调用; 这可能不是你想要的.特别是,当从Derived(const std :: string&)构造函数调用Initialize()时,该对象仍然"仅"是Derived对象而不是SubDerived对象(因为构造代码的SubDerived层没有开始了)这就是为什么Derived :: Do()会被调用而不是SubDerived :: Do().
问:如果子类使用相同的委托模式来确保所有内容以相同的方式实例化,该怎么办?
答:这主要是有效的,但只有在调用SubDerived :: Do()之前调用Derived :: Do()是可以的.
特别是,假设您使用的是SubDerived类,其功能与上面的Derived相同.然后当调用代码执行此操作时:
SubDerived foo("Hello");
Run Code Online (Sandbox Code Playgroud)
将发生以下调用序列:
Base()
Derived()
Derived(const std::string &)
Base::Initialize()
Derived::Do()
SubDerived()
SubDerived(const std::string &)
Base::Initialize()
SubDerived::Do()
Run Code Online (Sandbox Code Playgroud)
...是的,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() {}
};
Run Code Online (Sandbox Code Playgroud)
这里的调用在委托的构造函数调用中没有豁免,因为此时仍然没有构造对象的更多派生部分.
struct Base
{
Base()
{
foo(); // still UB
}
Base(int) : Base() {};
virtual void foo() = 0;
};
struct Derived : Base
{
virtual void foo() {}
};
Run Code Online (Sandbox Code Playgroud)
这是您引用的维基百科文章:
一旦任何构造函数完成执行,C++ 11就会考虑构造一个对象.由于将允许多个构造函数执行,这意味着每个委托构造函数将在其自己类型的完全构造的对象上执行.派生类构造函数将在其基类中的所有委托完成后执行.
关键是第二个粗体句子,而不是第一个,因为人们可能会从快速浏览中误解.
但是,您发布的代码片段很好,这是因为派生的构造函数体正在执行,而不是抽象类的构造函数,它已经完全构造.也就是说,您必须要求证明其安全的事实应该表明这不是最具表现力或最直观的方法,我会尽量避免在您的设计中使用它.