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

Когда безопасно вызывать this-> в конструкторе и деструкторе

Я до сих пор не смог найти окончательного ответа на этот вопрос. Когда безопасно вызывать this-> изнутри объекта. И, в частности, изнутри конструктора и деструктора.

А также при использовании общего наследования. Безопасно ли использовать результат и понижение в результате этого вызова?

Итак, например:

class foo
{
   foo():
   a(),
   b(this->a)//case 1
   {
       this-> a = 5; //case 2
   }

   int a;
   int b;
};

class bar: public baz
{
   bar():
   baz(this)//case 3 - assuming baz has a valid constructor
   {


   }

}

И, наконец, самый маловероятный

foo()
   {
      if(static_cast<bar*>(this));//case 4
   }

Какие из перечисленных случаев являются законными?

Примечание: Я знаю, что многие из описанных выше методов нецелесообразны.

4b9b3361

Ответ 1

Внутри любой нестатической функции-члена this указывает на объект, на который была вызвана функция. Безопасно использовать до тех пор, пока это действительный объект.

Внутри тела конструктора или деструктора есть действительный объект класса, который в настоящее время строится. Однако, если это базовый под-объект некоторого производного класса, тогда только базовый под-объект действителен в это время; поэтому он вообще небезопасен для down-cast и пытается получить доступ к членам производного класса. По этой же причине вам нужно быть осторожным при вызове виртуальных функций здесь, так как они отправляются в соответствии с созданным или уничтоженным классом, а не конечным перегружателем.

В списке инициализатора конструктора вам нужно быть осторожным только для доступа к элементам, которые были инициализированы; то есть члены, объявленные перед тем, который в настоящее время инициализируется.

Приведение в базовый класс всегда безопасно, поскольку базовые под-объекты всегда инициализируются сначала.

Для конкретных примеров, которые вы только что добавили к вопросу:

  • Случай 1 хорош (если хрупкий), так как a был инициализирован в этой точке. Инициализация a со значением b будет undefined, так как b инициализируется после a.
  • case 2 в порядке: все члены были инициализированы в этой точке.
  • случай 3 не будет компилироваться, так как нет подходящего конструктора foo. Если бы это было так, то это зависело бы от того, что с ним сделал конструктор - независимо от того, пытался ли он получить доступ к элементам до их инициализации.
  • case 4 будет корректно сформирован, если вы добавили отсутствующий ), но опасный, если попытаетесь использовать указатель для доступа к объекту. this пока не указывает на действительный объект bar (только инициализирована часть foo), поэтому доступ к членам bar может привести к поведению undefined. Просто проверка того, является ли указатель ненулевым, прекрасен и всегда будет давать true (независимо от того, применяете ли вы бессмысленный листинг).

Ответ 2

В С++ супер-faq есть хорошая запись:

https://isocpp.org/wiki/faq/ctors#using-this-in-ctors

Некоторые люди считают, что вы не должны использовать этот указатель в конструкторе потому что объект еще не сформирован полностью. Однако вы можете использовать это в конструкторе (в {body} и даже в списке инициализации) если вы осторожны.

Вот что всегда работает: {body} конструктора (или функция, вызываемая из конструктора) может надежно получить доступ к данным члены, объявленные в базовом классе и/или члены данных, объявленные в конструкторы собственного класса. Это связано с тем, что все эти элементы данных должны быть полностью построены к тому моменту, когда конструкторы {body} запускают выполнение.

Вот что никогда не работает: {body} конструктора (или функция, вызываемая из конструктора) не может перейти к производному класса, вызвав функцию виртуального члена, которая переопределена в производного класса. Если бы ваша цель состояла в том, чтобы перейти к переопределенной функции в производный класс, вы не получите то, что хотите. Обратите внимание, что вы не будете перейти к переопределению в производном классе независимо от того, как вы вызываете виртуальная функция-член: явно используя этот указатель (например, this- > method()), неявно используя этот указатель (например, метод()), или даже вызов какой-либо другой функции, вызывающей виртуального участника на ваш объект. Суть заключается в следующем: даже если вызывающий объект создает объект производного класса во время конструктор базового класса, ваш объект еще не класс. Вы были предупреждены.

Вот что-то, что иногда срабатывает: если вы передаете какую-либо из данных членов этого объекта к другому инициализатору элементов данных, вы должны убедитесь, что другой элемент данных уже инициализирован. Хорошей новостью является то, что вы можете определить, имеет ли другой член данных (или не был) инициализирован с использованием некоторого простого языка правила, которые не зависят от конкретного используемого вами компилятора. Плохая новость заключается в том, что вы должны знать эти языковые правила (например, базовые сначала инициализируются подэлементы класса (смотрите порядок, если у вас есть множественное и/или виртуальное наследование!), то элементы данных, определенные в класс инициализируется в том порядке, в котором они появляются в класс). Если вы не знаете эти правила, данных из этого объекта (независимо от того, вы или нет явно использовать это ключевое слово) для любых других элементов данных инициализатор! И если вы знаете правила, будьте осторожны.

Ответ 3

Этот указатель доступен в каждой нестатической функции-члене...

§9.3.2/1

В теле нестатической (9.3) функции-члена ключевое слово this является выражением prvalue, значением которого является адрес объекта, для которого вызывается функция. Тип этого в членной функции класса X есть X *. Если функция-член объявлена ​​const, тип этого - const X *, если функция-член объявлена ​​изменчивой, тип этого является изменчивым X *, и если функция-член объявлена ​​const volatile, тип этого const изменчивый X *.

... где конструкторы и деструкторы являются функциями-членами...

§12/1

Конструктор по умолчанию (12.1), конструктор копирования и оператор присваивания копии (12.8), оператор перемещения и оператор назначения перемещения (12.8), а деструктор (12.4) - специальные функции-члены.

... которые не являются статическими.

§12.1/4

Конструктор не должен быть виртуальным (10.3) или статическим (9.4).

§12.4/2

Деструктор не должен быть статическим.

Таким образом, this доступен в конструкторах и деструкторах. Но есть ограничения (особенно в отношении использования this внутри списка инициализаторов).

(Примечание. Внутри тела конструктора/деструктора завершена инициализация всех подобъектов и элементов, и они доступны, см. ниже ниже).

1. Создается только объект доступа (или их подобъекты) через this.

§12.1/14

Во время построения объекта const, если к объекту или к любому из его подобъектов обращается через значение gl, которое не получается прямо или косвенно из указателя конструктора this, значение объекта или подобъект, полученный таким образом, не определен.

2. Не вызывайте виртуальные функции, которые переопределяются в производном классе в базовом конструкторе

§12.7/4

Функции членов, включая виртуальные функции (10.3), могут быть вызваны во время строительства или уничтожения (12.6.2). Когда виртуальная функция вызывается прямо или косвенно из конструктора или из деструктора, в том числе при построении или уничтожении нестатических элементов данных классов, а объектом, к которому применяется вызов, является объект (назовите его x) под строительство или разрушение, вызываемая функция является окончательным перераспределением в классе конструкторов или деструкторов, а не одной, переопределяющей его в более производном классе. Если вызов виртуальной функции использует явный член класса (5.2.5), а выражение объекта относится к полному объекту x или к одному из этих подобъектов базового класса объектов, но не к x или одному из его субобъектов базового класса, поведение undefined.

3. Не применяйте dynamic_cast для приведения this к любому типу, отличному от типа, находящегося в разработке, или к его базовому типу.

§12.7/6

dynamic_casts (5.2.7) может использоваться при строительстве или разрушении (12.6.2). Когда dynamic_cast используется в конструкторе (включая инициализатор mem или инициализатор скобок или равный для нестатического элемента данных) или в деструкторе или используется в функции, вызываемой (прямо или косвенно) из конструктора или деструктор, если операнд dynamic_cast ссылается на объект, находящийся в стадии разработки или уничтожения, этот объект считается наиболее производным объектом, который имеет тип класса конструктора или деструктора. Если операнд dynamic_cast ссылается на объект, который находится под конструированием или разрушением, а статический тип операнда не является указателем на объект или объект собственного класса конструктора или деструктора или одного из его оснований, dynamic_cast приводит к поведению undefined.

4. Преобразование this в указатель базового типа допускается только через пути, состоящие из построенных базовых типов.

§12.7/3

Чтобы явно или неявно преобразовать указатель (glvalue), ссылающийся на объект класса X на указатель (ссылку) на прямой или косвенный базовый класс B из X, построение X и построение всех его прямых или косвенные основания, прямо или косвенно вытекающие из B, должны начаться, и уничтожение этих классов не будет завершено, в противном случае преобразование приведет к поведению undefined. Чтобы сформировать указатель на (или получить доступ к значению) прямой нестатический член объекта obj, должно начаться построение obj, и его уничтожение не будет завершено, в противном случае вычисление значения указателя (или доступа к члену значение) приводит к поведению undefined.

Доступ к под-объектам и элементам в списке инициализаторов и тело конструктора

В принципе вы можете получить доступ к построенным/инициализированным объектам из списка инициализаторов, если их инициализация происходит до их доступа. Порядок инициализации

§12.6.2/10

В конструкторе без делегирования инициализация выполняется в следующем порядке:

  • Во-первых, и только для конструктора самого производного класса (1.8) виртуальные базовые классы инициализируются в том порядке, в котором они появляются на первом пересечении слева направо влево-вправо направленного ациклического графа базы классы, где "слева направо" - это порядок появления базовых классов в базовом-спецификационном списке производного класса.

  • Затем прямые базовые классы инициализируются в порядке объявления, как они появляются в списке-спецификаторе-базе (независимо от порядка mem-инициализаторов).

  • Затем нестатические элементы данных инициализируются в том порядке, в котором они были объявлены в определении класса (опять же, независимо от порядка инициализаторов mem).

  • Наконец, выполняется составная инструкция тела конструктора.