Означает ли возвращенная временная копия все еще достаточно длительное время жизни t - даже если она гарантированно будет удалена?
t
будет на foo
'с телом, и выбрасывание происходит в теле make_tmp
.Таким образом, на жизнь t
не влияет elision - это тело foo
, каким бы то ни было образом, временным, статическим, динамическим или каким-либо другим.
Рассмотрим вариант foo, данныйниже.Я предполагаю, копирование elision больше не требуется (но весьма вероятно).Если копия не будет опущена, стандарт распространяется на нас (рассмотренные выше аргументы).В случае, если копия исключена, стандарт все еще гарантирует достаточное время жизни t несмотря на то, что тип возвращаемого выражения отличается от типа возвращаемого foo?
make_tmp().c_str()
эквивалентно std::string(make_tmp().c_str())
в исходном фрагменте вызов конструктора std::string
происходит неявно.Как вы упомянули в начале своего поста, исключение действительно происходит.
Я думаю, чтобы понять гарантии выбора, гораздо лучше следовать пониманию того, как логика возврата работает на уровне сборки.Это даст вам понимание того, как компилятор создает механизм возврата вызова, стандарт здесь просто пытается не отставать от фактической реализации компилятора, давая ясность, а скорее вводит некоторую новую концепцию синтаксиса языка.
Простой пример:
std::string foo();
int main() {
auto t = foo();
}
При сборке соответствующая деталь тело main
будет выглядеть следующим образом:
0000000000400987 <main>:
....
; Allocate 32-byte space (the size of `std::string` on x64) on the stack
; for the return value
40098b: 48 83 ec 20 sub $0x20,%rsp
; Put the pointer of the stack allocated chunk to RAX
40098f: 48 8d 45 e0 lea -0x20(%rbp),%rax
; Move the pointer from RAX to RDI
; RDI - is a first argument location for a callee by the calling convention
; By calling convention, the return of not trivial types (`std::string` in our case)
; must be taken care on the caller side, it must allocate the space for the return type
; and give the pointer as a first argument (what of course, is hidden by the compiler
; for C/C++)
400993: 48 89 c7 mov %rax,%rdi
; make a call
400996: e8 5b ff ff ff callq 4008f6 <foo()>
; At this point you have the return value at the allocated address on the main's stack
; at RBP - 32 location. Do whatever further.
....
В действительности получается, что пространство t
уже находится настек вызывающего (main
) и адрес этой памяти стека передается вызываемому, foo
.foo
Нужно только вставить материал по какой-то логике, и это все.foo
может выделить некоторую память для сборки std::string
, а затем скопировать эту память в заданную память, но также может (что во многих случаях легко оптимизировать) просто напрямую работать с данной памятью без выделения чего-либо.В последнем случае компилятор может вызывать конструктор копирования, но это не имеет смысла.Стандарт C ++ 17 только разъясняет этот факт.