Скопируйте Elision для возвращенных временных - PullRequest
0 голосов
/ 25 мая 2018

Я пытаюсь понять срок жизни, гарантированный стандартом C ++ 17, в частности для гарантированных разрешений копирования.Давайте начнем с примера

std::string make_tmp();

std::string foo() {
   return std::string{make_tmp().c_str()};
}

Мое понимание того, что происходит : make_tmp создает временную string, которую мы назовем t;foo возвращает (ненужно созданный) временный (копия t c_str).Стандарт (даже до C ++ 17) гарантирует, что время жизни t будет временем до полного вычисления выражений возврата.Таким образом, это безопасно, поэтому создайте временную копию t (подлежит возврату).

Now copy elisions активируется;более конкретно, второй маркер в первом блоке C ++ 17:

В вызове функции, если операнд оператора return является prvalue, а тип возврата функции такой же, кактип этого prvalue.

Следовательно, временная копия даже не будет создана вообще.

Дополнительные вопросы:

  1. Означает ли возвращенная временная копия все еще достаточно длительный срок действия t - даже если гарантированно будет исключено?

  2. Рассмотрим вариант foo, приведенный ниже.Я предполагаю, копирование elision больше не требуется (но весьма вероятно).Если копия не будет опущена, стандарт распространяется на нас (рассмотренные выше аргументы).В случае исключения копии, стандарт все еще гарантирует достаточное время жизни t, несмотря на то, что тип выражения return отличается от типа возвращаемого значения foo?

foo -Вариант:

std::string foo() {
   return make_tmp().c_str();
}

Я хотел бы понять гарантии, подразумеваемые стандартом.Обратите внимание, что мне известно о том, что обе foo версии "работают" (т. Е. Нет повисших указателей, даже при тестировании с пользовательскими классами под различными компиляторами).

Ответы [ 3 ]

0 голосов
/ 25 мая 2018

Этот ответ напрямую отвечает на вопросы о жизни, задаваемые в OP (и вы можете видеть, что он не имеет никакого отношения к разрешению копирования).Если вы не знакомы со всей историей, произошедшей во время выполнения оператора return, вы можете сослаться на ответ Барри.


Да, временное действие гарантированно сохранится во время инициализации копирования возвращенногообъект на [stmt.return] / 2:

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

0 голосов
/ 25 мая 2018

Означает ли возвращенная временная копия все еще достаточно длительное время жизни 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 только разъясняет этот факт.

0 голосов
/ 25 мая 2018

Я думаю, здесь есть некоторая путаница в отношении , из которых копий выбрасываются.Давайте возьмем самый дальний вид:

std::string make_tmp();

std::string foo() {
   return std::string{make_tmp().c_str()};
}

std::string s = foo();

Здесь потенциально создано четыре std::string с: make_tmp(), временный std::string{...}, созданный из него, возвращаемый объектfoo() и s.Это подразумевает три копии (я просто собираюсь использовать слово copy для согласованности, даже если все они являются ходами. Надеюсь, это не будет сбивать с толку).

Копия elision позволяет удалить два изэти копии:

  • Копия из std::string{...} в возвращаемый объект foo().
  • Копия из foo() в s

Обе эти элисии обязательны в C ++ 17 «гарантированная элиминация копии» - потому что мы инициализируем из prvalue(термин, который немного сбивает с толку, поскольку мы на самом деле не выполняем разрешение перегрузки, чтобы определить, что нам нужно выполнить построение копии, а затем пропустить его, мы просто непосредственно инициализируем).Код идентичен:

std::string s{make_tmp().c_str()};

Это не может быть удалено, хотя - мы все еще создаем string через make_tmp(), вытаскиваем его содержимое, а затем создаем из них новый string,Обойти это невозможно.

Предоставленный вариант имеет точно такое же поведение.

...