Инициализация возвращаемого значения происходит в контексте вызываемого объекта (функции, содержащей оператор return
). То есть, если вы хотите оставить открытой возможность обработки исключения, создаваемого конструктором по умолчанию T
, вам не следует не объявлять myfunc
с noexcept
.
Я понимаю источник путаницы: согласно таксономии категорий значений в С++ 17 и более поздних версиях, prvalue — это рецепт создания объекта, а не сам объект. Рассмотрим следующий код:
T foo() {
return {};
}
T t = foo();
В C++14 оператор return
и инициализация t
представляют собой два отдельных шага, хотя исключение разрешено в качестве оптимизации. На первом этапе возвращаемый объект (он же foo()
) инициализируется копированием из {}
. На втором этапе t
инициализируется копированием из этого возвращаемого объекта. Очевидно, что первый шаг происходит в контексте вызываемого объекта, а второй — в контексте вызывающего.
Таким образом, вы можете подумать, что в C++17 происходит аналогичный двухэтапный процесс, только с пересмотренной концепцией prvalue: а именно, поскольку foo()
является prvalue, вы можете подумать, что оператор return
просто создает рецепт (который может быть концептуально представлен как [](void* p) { new (p) T{}; }
), и указанный рецепт создается в контексте вызываемого объекта, в то время как выполнение этого рецепта для создания t
будет происходить в контексте вызывающего объекта. Если бы это было так, то фактический вызов конструктора по умолчанию T
происходил бы в контексте вызывающего объекта, и, таким образом, любое выброшенное им исключение не встречало бы внешней скобки вызываемого объекта.
Однако в стандарте есть явный язык, который отрицает такую интерпретацию:
оператор return инициализирует объект результата glvalue или prvalue вызова функции (явного или неявного) путем инициализации копирования [...] из операнда.
То есть инициализация t
выполняется самим оператором return
. Это означает, что t
полностью инициализируется до того, как самый внешний блок вызываемого объекта будет фактически оставлен. Например, если в вызываемом объекте есть какие-либо локальные переменные, которые необходимо уничтожить, это фактически происходит после того, как t
уже инициализирован (поэтому такое поведение потенциально отличается от поведения C++14). Точно так же, как ясно, что уничтожение таких локальных переменных происходит в контексте вызываемого объекта (и, следовательно, поиск обработчика, если при этом будет выдано исключение, натолкнется на самый внешний блок foo
), также ясно, что инициализация t
происходит в контексте вызываемого объекта.
person
Brian Bi
schedule
31.01.2021