Подтвердить что ты не робот

Является ли законным делать это на `Derived * 'в базовом ctor-инициализаторе?

Учитывая следующий пример CRTP:

template <typename T>
int foo(T* const)
{
   return 0;
}

template <typename Derived>
struct Base
{
   Base() : bar(foo(static_cast<Derived*>(this)) {};
   int bar;
};

struct Derived1 : Base<Derived1> {};

Является ли преобразование this в Derived* действительным? Я, кажется, помню, что это может быть не так, но теперь не может найти конкретных доказательств этого.

"Натуральный" тип this на этом этапе равен Base* const, и, конечно же, есть некоторые случаи, когда даже статическое литье указателя this во время инициализации не в порядке, например, повышение до завершения базовой конструкции (12.7/3).

@DeadMG говорит:

существует явное исключение в стандарте w.r.t. получив это в списке инициализаторов. это для передачи указателей на себя в подобъекты.

12.6.2/12 говорит:

[Примечание. Поскольку mem-инициализатор оценивается в области конструктора, этот указатель может использоваться в списке выражений mem-initializer для обращения к инициализированному объекту. -end note]

... хотя этого недостаточно, чтобы сказать, что преобразование в Derived* действительно.

Моя интуиция заключается в том, что на этой фазе инициализации объекта this не указывает на экземпляр Derived и, как таковой, даже просто указатель на него с типом Derived*, строго говоря, UB. Это потому, что это не действительный указатель или нулевой указатель.

(Это потенциально имеет практические последствия для таких подходов, как this, хотя в этом ответе и моем примере выше все это можно было бы отбросить, просто написав static_cast<Derived*>(0) вместо этого.)

4b9b3361

Ответ 1

Я думаю, что это UB.

Как сказал Майк:

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

и это моя интерпретация, что это не один из этих способов.

Более формально:

[C++11: 3.8/1]: Время жизни объекта - это свойство времени выполнения объекта. Говорят, что объект имеет нетривиальную инициализацию, если он имеет тип класса или агрегата, и он или один из его членов инициализируется конструктором, отличным от тривиального конструктора по умолчанию. [Примечание: инициализация по тривиальный конструктор копирования/перемещения является нетривиальной инициализацией. - end note] Время жизни объекта типа T начинается, когда:

  • получено хранилище с правильным выравниванием и размером для типа T и
  • , если объект имеет нетривиальную инициализацию, его инициализация завершена.

и

[C++11: 3.8/5]: До того, как объект жизни запустился, но после того, как хранилище, которое будет занимать объект, было выделено или, после того, как срок жизни объекта закончился и перед хранилищем, которое объект занятый повторно или освобожден, любой указатель, который ссылается на место хранения, в котором будет находиться или находится объект, может использоваться, но только ограниченным образом. Для объекта, находящегося в стадии разработки или уничтожения, см. 12.7. В противном случае, такой указатель относится к выделенному хранилищу (3.7.4.2) и с использованием указателя, как если бы указатель имел тип void*, четко определен. Такой указатель может быть разыменован, но полученное значение может использоваться только ограниченным образом, как описано ниже. В программе есть поведение undefined, если:

  • объект будет или имеет тип класса с нетривиальным деструктором, а указатель используется как операнд выражения-удаления,
  • указатель используется для доступа к нестатическому элементу данных или вызова нестатической функции-члена объекта или
  • указатель неявно преобразован (4.10) в указатель на тип базового класса или
  • указатель используется как операнд static_cast (5.2.9) (кроме случаев, когда преобразование соответствует void* или void*, а затем char* или unsigned char*), или
  • указатель используется как операнд dynamic_cast [..]