Скопировать исключение возвращаемых значений и noexcept

У меня есть такой шаблон функции:

template <typename T>
constexpr auto myfunc() noexcept
{
    return T{};
}

Этот шаблон функции гарантированно не будет исключением из-за исключения копирования? Если исключение выдается внутри конструктора, происходит ли это внутри или вне функции?

3 ответа

Решение

Все, что делает elision copy, - это исключает фактическую копию или перемещение. Все происходит так, как если бы все происходило без исключения копирования (конечно, за исключением самой копии).

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

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

Если конструктор копирования / перемещения выдает исключение, поскольку копирование / перемещение не происходит, все продолжается.

С gcc 7.3.1, скомпилированным с использованием -std= C++17:

template <typename T>
constexpr auto myfunc() noexcept
{
    return T{};
}

class xx {
public:

    xx() { throw "Foo"; }
};

int main()
{
    try {
        myfunc<xx>();
    } catch (...) {
    }
}

Результат:

terminate called after throwing an instance of 'char const*'

Теперь давайте смешаем это и сгенерируем исключение в конструкторах копирования и перемещения:

class xx {
public:

    xx() { }

    xx(xx &&) { throw "Foo"; }

    xx(const xx &) { throw "Baz"; }
};

Это работает без исключения.

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

Я понимаю источник путаницы: согласно таксономии категорий значений в С++17 и более поздних версиях, prvalue — это рецепт создания объекта, а не сам объект. Рассмотрим следующий код:

      T foo() {
    return {};
}
T t = foo();

В С++ 14 оператор и инициализация — это два отдельных шага, хотя исключение разрешено в качестве оптимизации. На первом этапе возвращаемый объект ( он же "") инициализируется копированием из . На втором этапе выполняется копирование-инициализация из этого возвращаемого объекта. Очевидно, что первый шаг происходит в контексте вызываемого объекта, а второй — в контексте вызывающего.

Таким образом, вы можете подумать, что в C++17 происходит аналогичный двухэтапный процесс, только с пересмотренной концепцией prvalue: а именно, поскольку является prvalue, вы можете подумать, что оператор просто создает рецепт (который может быть концептуально представлен как ) и указанный рецепт создается в контексте вызываемого объекта, в то время как выполнение этого рецепта для создания будет происходить в контексте вызывающего объекта. Если бы это было так, то фактический вызов конструктора по умолчанию произойдет в контексте вызывающего объекта, и, таким образом, любое исключение, созданное им, не встретит внешнюю фигурную скобку вызываемого объекта.

Однако в стандарте есть явный язык, который отрицает такую ​​интерпретацию:

оператор return инициализирует объект результата glvalue или prvalue вызова функции (явного или неявного) путем инициализации копирования [...] из операнда.

То есть инициализация выполняется само заявление. Это означает, что он полностью инициализируется до того, как самый внешний блок вызываемого объекта будет фактически оставлен. Например, если в вызываемом объекте есть какие-либо локальные переменные, которые необходимо уничтожить, это фактически происходит после того, как они уже были инициализированы (поэтому такое поведение потенциально отличается от поведения C++14). Так же как ясно, что уничтожение таких локальных переменных происходит в контексте вызываемого объекта (и, следовательно, поиск обработчика, если бы при этом было выброшено исключение, наткнулся бы на самый внешний блок ), также ясно, что инициализация происходит в контексте вызываемого объекта.

Сделай это так:

template <typename T> constexpr 
auto myfunc() noexcept(std::is_nothrow_default_constructible_v<T>)
{
    return T{};
}
Другие вопросы по тегам