Что совсем не означает конструкторы?

В соответствии со стандартом С++, что именно спецификация noexcept noexcept для конструктора классов применяется к?

  • тело функции?
    • инициализация членов в необязательном ctor-инициализаторе?
      • инициализация базовых классов в необязательных mem-инициализаторах?
      • инициализация членов класса в необязательных mem-инициализаторах?
    • составной оператор?
    • функция-try-block?
  • инициализация базовых классов объектов, не инициализированных в ctor-инициализаторе?
  • инициализация элементов класса объектов, не инициализированных в ctor-инициализаторе?
  • что-то дополнительное?

Другими словами, какое из перечисленных выше охватывает спецификация noexcept noexcept-спецификация (т.е. триггер std::terminate() при бросании исключения, если noexcept(true))?

Просьба привести ссылки на стандарт. Также приветствуются советы по любым оговоркам с использованием noexcept для конструкторов. Спасибо!

Ответ 1

Другими словами, какое из перечисленных выше включает в себя noexcept noexcept-specification...?

Спецификация исключения (noexcept и динамическая спецификация исключений) относится к построению базовых классов, построению и инициализации членов и коду в теле конструктора. В принципе, все функции, выполняемые при построении объекта, - это имеет смысл в том, что спецификация исключения привязана к конструктору объекта, поэтому она должна охватывать код, выполняемый во время построения объекта; это было бы противоречиво, если бы какая-либо часть конструкции не была покрыта этим.

Поддержка стандартных котировок...

Что делать, если при построении (и, возможно, не обрабатывается) исключение возникает?

[except.spec]/9

Всякий раз, когда создается исключение типа E, и поиск обработчика ([except.handle]) встречает самый внешний блок функции с спецификацией исключения, которая не позволяет E, тогда

  • Если определение функции имеет спецификацию динамического исключения, вызывается функция std::unexpected() ([except.unexpected]),
  • в противном случае вызывается функция std::terminate() ([except.terminate]).

Что означает "внешний блок функции"? Тело функции. 1

спецификация исключений выше содержит спецификацию noexcept.

Как определяется неявно объявленная спецификация исключений для неявно объявленного конструктора?

[except.spec]/15

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

  • если f - конструктор,

    • множества потенциальных исключений вызовов конструктора

      • для X невариантных нестатических членов данных,
      • для X прямых базовых классов и
      • если X не является абстрактным ([class.abstract]), для X виртуальных базовых классов

        (включая выражения аргументов по умолчанию, используемые в таких вызовах), выбранных с помощью разрешения перегрузки для неявного определения f ([class.ctor])...

    • наборы возможных исключений инициализации нестатических элементов данных из инициализаторов скобок или равных, которые не игнорируются ([class.base.init]);

Это дает очень полезное разъяснение того, что компилятор будет использовать для определения (и, следовательно, рассмотренного) спецификации исключения.


1 Что означает "внешний блок функции"? Было высказано замечание относительно озабоченности определением блока функции. Стандарт не имеет формального определения блока функции. Блок фразы функции используется только в Обработка исключений [кроме]. Фраза была включена в стандарт еще с С++ 98.

Для большей ясности по этому вопросу нам потребуется искать альтернативный источник и делать некоторые разумные выводы.

Из Глоссарий Stroustrup С++;

тело функции - самый внешний блок функции. См. Также: try-block, определение функции. TС++ PL 2.7, 13.

И из [dcl.fct.def.general]/1 грамматика для тела функции, которая покрывает ctor-инициализатор соединением -statement и функция-try-block;

Определения функций имеют вид:

...

  функция тела:
    ctor-initializer opt составная заявка
    функция попробуйте-блок

...

Любая неофициальная ссылка на тело функции должна интерпретироваться как ссылка на нетерминальное тело функции...

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

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


Некоторые эмпирические данные, приведенные ниже, для построения a1, a2 и a3 (когда остальные комментируются), вызывают вызов std::terminate. Результат применим для g++, clang и MSVC.

struct Thrower { Thrower() { std::cout << "throwing..." << std::endl; throw 42; } };

struct AsMember { Thrower t_; AsMember() noexcept : t_{} { std::cout << "ctor" << std::endl; } };

struct AsBase : Thrower { AsBase() noexcept { std::cout << "ctor" << std::endl; } };

struct AsNSDMI { Thrower t_ {}; AsNSDMI() noexcept { std::cout << "ctor" << std::endl; } };

int main()
{
    std::set_terminate([](){ std::cout << "terminating..." << std::endl; });
    try {
        //AsMember a1{};
        //AsBase a2{};
        AsNSDMI a3{};
    }
    catch (...) { std::cout << "caught..." << std::endl; }
    return 0;
}

Ответ 2

Вероятно, вы можете ответить на этот вопрос самостоятельно, если вы рассматриваете конструктор noexcept, чье определение недоступно в текущем блоке перевода (например, элемент-конструктор находится в общей библиотеке).

Если noexcept не применимо ко всему конструктору, исключение все равно может быть выведено конструктором каким-либо образом, победив цель noexcept.

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