С++ 11 Делегированный конструктор Чистый виртуальный метод и вызовы функций - опасности? - программирование

С++ 11 Делегированный конструктор Чистый виртуальный метод и вызовы функций - опасности?

Не дубликат Вызов виртуальной функции и чисто виртуальной функции из конструктора:

Бывший вопрос относится к С++ 03, а не к поведению Делитера конструктора в С++ 11, и этот вопрос не рассматривает смягчение поведения undefined, используя делегирование, чтобы обеспечить правильное построение до того, как будут выполнены чистые виртуальные реализации.

В С++ 11, каковы опасности вызова функций Pure Virtual в конструкторе класса во время построения, но после того, как класс/объект был "полностью построен" через делегирование конструктора?

По-видимому, где-то в спецификации С++ 11 такое ограничение существует,

Функции-члены (включая виртуальные функции-члены, 10.3) могут быть призвал к строительству объекта. Аналогичным образом, объект, конструкцией может быть операнд оператора типа. - 12.6.2 # 13 [С++ Working Draft] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf) Не удается найти версию "Справедливого использования" опубликованной спецификации.

С++ 11 рассматривает объект, построенный после завершения любого конструктора выполнение. Поскольку несколько конструкторов будут разрешены для выполнения, это будет означать, что каждый конструктор делегата будет выполняться на полностью построенный объект своего типа. Производные конструкторы классов будет выполняться после завершения делегирования в базовых классах. - Wikipedia, говоря, что это вещь С++ 11.

Фактический С++ 11 Ссылка неизвестна.

Следующий пример компиляции и RUNS в Nov CTP Visual Studio 2012 Компилятор С++:

#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;
}
4b9b3361

Ответ 1

При обновлении код примера выглядит мне хорошо, с оговоркой, что если вы когда-либо создадите подкласс Derived, переопределение подкласса Do() не будет вызвано Derived (const std::string &), скорее Derived:: Do() по-прежнему будет вызван; который может быть не таким, каким вы хотели. В частности, когда Initialize() вызывается из конструктора Derived (const std::string &), объект по-прежнему остается "только" производным объектом, а не объектом SubDerived (поскольку уровень строительного кода SubDerived не имеет началось еще), и именно поэтому Derived:: Do() будет вызываться, а не SubDerived:: Do().

В: Что делать, если подкласс использует один и тот же шаблон делегирования, чтобы гарантировать, что все создается таким же образом?

A: Это будет работать в основном, но только если это нормально для Derived:: Do() для вызова до вызова SubDerived:: 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() вручную на объекте после создания объекта. Это немного больше работает для вызывающего кода, но преимущество в том, что вы можете избежать не очень очевидной или удобной семантики, которая вступает в игру при совершении виртуальных вызовов метода на частично построенных объектах.

Ответ 2

В типичном сценарии наследования с одним конструктором, UB должен вызывать чистую виртуальную функцию в базовом конструкторе:

[C++11: 10.4/6]: Функции-члены могут быть вызваны из конструктора (или деструктора) абстрактного класса; эффект виртуального вызова (10.3) на чистую виртуальную функцию, прямо или косвенно, для создаваемого (или уничтоженного) объекта от такого конструктора (или деструктора) составляет undefined.

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() {}
};

Вот цитата из Википедии, которую вы указали:

С++ 11 рассматривает объект, построенный после завершения любого конструктора. Поскольку нескольким конструкторам разрешено выполнять, это будет означать, что каждый конструктор делегата будет выполняться на полностью построенном объекте своего типа. Производные конструкторы классов будут выполняться после завершения делегирования в базовых классах.

Ключ - это второе смелое предложение, а не первое, так как вы можете ошибочно взглянуть на него.

Однако ваш опубликованный фрагмент кода прекрасен, и это потому, что выполняется производное тело конструктора, а не конструктор абстрактного класса, который уже был полностью построен. Тем не менее, тот факт, что вам пришлось попросить доказательства того, что это безопасно, должно быть некоторым признаком того, что это не самый выразительный или интуитивный подход, и я постараюсь избежать его в вашем дизайне.