c ++ 11 — C ++ 11 делегированный конструктор Pure Virtual Method & amp; Вызовы функций — опасность?

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

Предыдущий вопрос относится к C ++ 03, а не к новому поведению делегирования конструктора в C ++ 11, и этот вопрос не касается смягчения неопределенного поведения путем использования делегирования для обеспечения правильной конструкции перед выполнением чисто виртуальных реализаций.

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

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

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

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

Актуальный C ++ 11 Ссылка неизвестна.

Следующий пример компилирует и запускает в ноябре CTP компилятора Visual Studio 2012 C ++:

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

14

Решение

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

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

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

8

Другие решения

В типичном сценарии наследования с одним конструктором 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 рассматривает объект, созданный после завершения выполнения любого конструктора. Поскольку разрешено выполнение нескольких конструкторов, это будет означать, что каждый конструктор делегата будет выполняться для полностью сконструированного объекта своего собственного типа. Конструкторы производных классов будут выполняться после завершения делегирования в их базовых классах.

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

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

5

По вопросам рекламы [email protected]