что такое подвыражение инициализации

#c #initialization #c 17 #language-lawyer #exception-specification

#c #инициализация #c 17 #язык-юрист #исключение-спецификация

Вопрос:

 struct B {
  B() throw();
  B(const Bamp;) = default;        // implicit exception specification is noexcept(true)
  B(Bamp;amp;, int = (throw Y(), 0)) noexcept;
  ~B() noexcept(false);
};
int n = 7;
struct D : public A, public B {
    int * p = new int[n];
    // D​::​D() potentially-throwing, as the new operator may throw bad_­alloc or bad_­array_­new_­length
    // D​::​D(const Damp;) non-throwing
    // D​::​D(Damp;amp;) potentially-throwing, as the default argument for B's constructor may throw
    // D​::​ D() potentially-throwing
};
  

Рассмотрим приведенный выше код, который цитируется из except.spec #11. Я не сомневаюсь в спецификации исключений всех конструкторов D, за исключением D::D(Damp;amp;) , которая подчиняется следующему правилу:

Неявно объявленный конструктор для класса X или конструктор без спецификатора noexcept, который по умолчанию используется при его первом объявлении, имеет спецификацию исключения с возможностью возникновения исключений тогда и только тогда, когда любая из следующих конструкций является потенциально вызывающей:

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

Очевидно, что правило для создания D​::​D(Damp;amp;) спецификации потенциально вызывающего исключения не является ни первым, ни третьим элементом. Для первого правила выбран конструктор для инициализации базового подобъекта типа B is B(Bamp;amp;, int = (throw Y(), 0)) noexcept , который, как объявлено, имеет спецификацию исключения, не вызывающего исключения. Третье правило предназначено для конструктора по умолчанию. Таким образом, к этому случаю применимо только второе правило.

Однако, D​::​D(Damp;amp;) не должно иметь спецификации потенциально вызывающего исключения, за исключением того, что выражение throw Y() рассматривается как подвыражение D​::​D(Damp;amp;) .

Правило, определяющее непосредственное подвыражение, выглядит следующим образом:

Непосредственными подвыражениями выражения e являются

  1. составные выражения операндов e
  2. любой вызов функции, который e неявно вызывает,
  3. если e является лямбда-выражением, то инициализация объектов, захваченных copy, и составляющих выражений инициализатора инициализации-фиксирует,
  4. если e является вызовом функции или неявно вызывает функцию, составные выражения каждого аргумента по умолчанию, используемого в вызове, или
  5. если e создает агрегированный объект, составные выражения каждого инициализатора элемента по умолчанию ([class.mem]) используются при инициализации.

Подвыражение выражения e является непосредственным подвыражением e или подвыражением непосредственного подвыражения e.

Самый простой способ понять правило для подвыражения заключается в том, что оно работает рекурсивно, то есть,

immediate subexpression of immediate subexpression... of immediate subexpression of e является подвыражением e .

Я согласен, что выражение throw Y() является подвыражением функции B(Bamp;amp;, int = (throw Y(), 0)) noexcept из-за четвертого маркера. Однако я не знаю, B(Bamp;amp;, int = (throw Y(), 0)) noexcept рассматривается ли это как неявно вызываемая функция, которая вызывается выражением D​::​D(Damp;amp;) , которое, похоже, подчиняется второму пуле. если это так, пожалуйста, рассмотрите следующий код:

 class Test{
  Test(){}
  ~Test(){}
};
void func(){
  Test t{} // implicitly invoke the defautl constructor of `Test`
  // would implicitly  invoke the destructor of `Test`
}
int main(){
  func();
}
  

Итак, как я пишу в комментарии, конструктор и деструктор Test рассматриваются как подвыражения expression func() ? Если это не так, как интерпретировать формулировку a subexpression of such an initialization ? Итак, мои вопросы:

Вопрос 1:

Во втором примере неявно вызываемый конструктор или деструктор следует рассматривать как подвыражение expression func() ?

Вопрос 2:

Если ответ на первый вопрос отрицательный, то как интерпретировать a subexpression of such an initialization ?

Комментарии:

1. AFAIU, во втором примере вы не implicitly вызываете конструктор test , вы буквально запрашиваете создание объекта. С другой стороны, когда вы (или компилятор) определяете, D​::​D(Damp;amp;) даже если вы просто делаете D​::​D(Damp;amp;){} , это будет неявно вызывать конструктор базы. Я думаю, что разница здесь

2. @MartinMorterol В моем втором примере я просто определяю объект типа Test , я ни явно вызываю для него конструктор, ни вызываю для него деструктор. IIUC, неявный вызов означает, что нет соответствующего вызова функции, где постфиксное выражение является именем функции.

3. «Я ни явно вызываю конструктор», ну, я не согласен (и я могу ошибаться), для меня Test t{}; определение и инициализация t путем вызова конструктора. Чтобы усилить мою точку зрения, если мы определим explicit Test(){} , второй пример все еще компилируется.

4. @MartinMorterol Обратите внимание Test t{}; , я не вызывал конструктор явно для t . Для Test t{} где {} находится инициализатор, а именно braced-list-init . Это не имеет значения для определения конструктора как explicit Test(){} , код все еще хорошо сформирован. потому что инициализация является прямой инициализацией списка. Вместо этого Test t = {}; было бы неправильно сформировано.

Ответ №1:

В [except.spec]/ 7.2, в частности, формулировка «подвыражение такой инициализации», что такое «такая инициализация»? Ответ заключается в том, что оно должно ссылаться на инициализацию, описанную в p7.1:

конструктор, выбранный с помощью разрешения перегрузки в неявном определении конструктора для класса X для инициализации потенциально сконструированного подобъекта, или

Это не относится к инициализации X . Текст в начале [except.spec]/7 ссылается только на «конструктор» X , а не на «инициализацию» X . Естественный способ чтения p7.2 заключается в том, что «такая инициализация» относится к единственному предыдущему упоминанию инициализации, которое находится в p7.1.

Таким образом, проблема здесь, при применении [except.spec] /7.2, заключается не в том, являются ли составные выражения аргументов по умолчанию конструктора move B подвыражениями D инициализации, а в том, являются ли они подвыражениями B инициализации, и ответ заключается в том, что они есть.

Хотя приведенное выше объяснение должно ответить на ваш главный вопрос, я также скажу, что я не думаю, что конструктор B считается «неявно вызываемым» конструктором D для целей [intro.execution]/10 . Если бы это было так, то это означало бы, что инициализация B подобъекта является подвыражением инициализации D . Вместо этого я считаю, что мы должны рассматривать инициализацию B подобъекта как полное выражение в [intro.execution]/12.3:

Полное выражение — это […] декларатор инициализации (пункт 11) или mem-инициализатор (15.6.2), включая составные выражения инициализатора, […]

Хотя формулировка этого неясна, я полагаю, что инициализация B подобъекта считается выполняемой mem-инициализатором для целей [intro.execution]. [class.copy.ctor]/14, который определяет поведение конструктора перемещения по умолчанию, явно не говорит о том, что базы и члены инициализируются, как будто с помощью mem-инициализатора. Однако было бы странно, если бы дефолтное определение конструктора перемещения имело иерархию подвыражений, отличную от соответствующего пользовательского конструктора.

Если мы рассматриваем неявный вызов конструктора базового класса как не являющийся mem-инициализатором, а скорее как «вызов функции, который e неявно вызывает» в [intro.execution] / 10.2, это будет означать, что первое не является полным выражением, и нет смысла уничтожать временные объекты в конце его. Было бы действительно странно, если бы это было так: это означало бы, что временные объекты, созданные во время выполнения конструктора перемещения по умолчанию, уничтожаются в разное время, чем временные объекты, созданные во время выполнения эквивалентного пользовательского конструктора перемещения. Простой эксперимент на Godbolt убеждает меня, что GCC и Clang не принимают такой странной интерпретации.

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

Ответ №2:

Для первого правила выбран конструктор для инициализации базового подобъекта типа B is B (Bamp;amp;, int = (throw Y(), 0)) noexcept , который, как объявлено, имеет спецификацию исключения, не вызывающего исключения. Это означает, что конструктор для B не будет выдавать при вызове. Однако перед D вызовами B он должен сгенерировать 2-й аргумент из своего значения по умолчанию, который будет выдавать. Соответственно, D будет выбрасываться не из-за B , а из-за параметра по умолчанию, который выполняется снаружи B .

Чтобы понять, что такое подвыражение инициализации, вам нужно рассмотреть код, сгенерированный компилятором. Ваш вызов func() не создает объект типа T в области вызова, поскольку компилятор ничего не вводит в область main . Объект создается в области func и это не считается.

Комментарии:

1. Вы не интерпретировали, что такое «подвыражение такой инициализации»