Я обновляю свои навыки C++ и заново изучаю семантику движений. Вот пример программы и вывод, который я не могу понять.
#include <iostream>
#include <iomanip>
int sepI = 0;
void printSep(const char* msg) {
std::cout
<< std::setw(40) << std::setfill('>') << "" << std::setfill(' ')
<< std::setw(2) << std::setiosflags(std::ios::right) << sepI++ << ": " << std::resetiosflags(std::ios::right)
<< std::setiosflags(std::ios::left) << msg << std::resetiosflags(std::ios::left)
<< std::endl;
}
void printHook(const char* msg, void* p) {
std::cout
<< std::setw(25) << std::setiosflags(std::ios::left) << msg << std::resetiosflags(std::ios::left)
<< std::setw(15) << std::setiosflags(std::ios::right) << p << std::resetiosflags(std::ios::right)
<< std::endl;
}
class MyT {
public:
MyT() { printHook("constructor call", this); }
MyT(MyT& t) { printHook("copy constructor call", this); }
MyT(MyT&& t) { printHook("move constructor call", this); }
~MyT() { printHook("destructor call", this); v = 0; }
MyT& operator=(MyT& t) { printHook("copy assigment call", this); return *this; }
MyT& operator=(MyT&& t) { printHook("move assigment call", this); return *this; }
int v = 1;
};
MyT fn() { return MyT(); }
int main() {
printSep("a");
MyT a;
printSep("MyT&& v1 = std::move(a)");
MyT&& v1 = std::move(a);
std::cout << v1.v << " " << &v1 << std::endl;
printSep("MyT&& v2 = MyT()");
MyT&& v2 = MyT();
std::cout << v2.v << " " << &v2 << std::endl;
printSep("MyT&& v3 = std::move(MyT())");
MyT&& v3 = std::move(MyT());
std::cout << v3.v << " " << &v3 << std::endl;
printSep("MyT&& v4 = fn()");
MyT&& v4 = fn();
std::cout << v4.v << " " << &v4 << std::endl;
printSep("MyT&& v5 = std::move(fn())");
MyT&& v5 = std::move(fn());
std::cout << v5.v << " " << &v5 << std::endl;
printSep("end");
return 0;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 0: a OK.
constructor call 0x7ffe656eeae0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 1: MyT&& v1 = std::move(a) OK. Makes sense.
1 0x7ffe656eeae0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 2: MyT&& v2 = MyT() OK. Makes sense.
constructor call 0x7ffe656eeae8
1 0x7ffe656eeae8
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 3: MyT&& v3 = std::move(MyT()) NOT OK.
constructor call 0x7ffe656eeaec
destructor call 0x7ffe656eeaec
1 0x7ffe656eeaec
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 4: MyT&& v4 = fn() NOT OK.
constructor call 0x7ffe656eeaf0
1 0x7ffe656eeaf0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5: MyT&& v5 = std::move(fn()) NOT OK.
constructor call 0x7ffe656eeaf4
destructor call 0x7ffe656eeaf4
1 0x7ffe656eeaf4
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 6: end
destructor call 0x7ffe656eeaf4
destructor call 0x7ffe656eeaf0
destructor call 0x7ffe656eeae8
destructor call 0x7ffe656eeae0
Вопросы к шагам 3, 4, 5.
3
Что здесь происходит?
Почему это не то же самое, что и в шаге 2?
Как так получилось, что по одному и тому же адресу 0x7ffe656eeaec
мы звонили в constructor - destructor - print
цепочку и не получили segfault?\
4 и 5
Откуда была выделена память?
Как мы получили 2 разных адреса экземпляра MyT из нединамических распределений в fn()
вызовах?
Это зарезервированная память для статических вызовов? Или они тогда размещаются в стеке? Например, компилятор видит, что это локальные переменные, и резервирует области памяти программы для таких вещей. Но я изучил дизассемблирование, и это был обычный вызов функции. Я не мог проверить это на динамических данных, так как я не могу создать массив rvalues.\
Неважно, какие флаги оптимизации я устанавливаю, результат один и тот же. Но вот команда сборки:
g++ -std=c++17 -g -Wall -O3 -pedantic
Обновление 1: уменьшен пример кода.
and didn't get segfault?
потому что это называется неопределенным поведением по какой-то причине.
В настоящее время этот вопрос включает в себя несколько вопросов в одном. Он должен сосредоточиться только на одной проблеме.
Я не уверен, почему вы думаете, что локальная переменная не может иметь другой адрес? Если вы вызываете его в другой функции, то они наверняка (скорее всего) будут другими.
Кстати, вопрос на самом деле не имеет ничего общего с семантикой движений. Если вы замените std::move
функцией, которая просто возвращает свой аргумент в качестве ссылки, и замените все ссылки rvalue ссылками const
lvalue, вы вернетесь в пред-C++11, прежде чем переместить семантику с тем же поведением. Речь идет о времени жизни временных объектов. См. godbolt.org/z/4MsacE643.
Краткий ответ, поскольку вопрос сейчас закрыт: временные объекты живут до конца полного выражения, в котором они проявляются, если только они не связаны немедленно, а не через вызов функции, со ссылкой, и в этом случае их время жизни продлевается до конца области ссылки. (Немедленно не совсем правильно, разрешены определенные промежуточные выражения, но не вызовы функций. Однако ссылки в параметрах функций также не уменьшат время жизни.)
std::move()
, параметр является локальной переменной std::move()
. Функция возвращает ссылку на локальную переменную, возвращенная ссылка на локальную переменную не продлевает жизнь объекта. Это неопределенное поведение.Вопрос был отредактирован. Исходный вопрос задавался о 10-м случае. Сейчас это 3.
&&
продлевает срок службы временного объекта.Разве 3 и 5 не равны случаю 4? Я имею в виду, с точки зрения времени жизни варов, все они временные. На 3 создается и перемещается (но перемещать некуда). В 4 он создается как локальная переменная вызова fn и должен быть уничтожен, но все же я смог получить к нему доступ. В 5 так же, как и в 4, это все же временно. Почему тогда && не продлевает срок службы для 3 и 5?
Нет, 4 — это совсем другой случай. && с временным объектом — нормально, && с возвращаемой локальной переменной — UB. fn
не создает и не возвращает локальную переменную. Пожалуйста, покажите имя этой локальной переменной. Жизнь локальной переменной не может быть продлена, потому что локальная переменная находится в стеке или регистре. Ссылка не может ссылаться на разрушенный фрейм локального стека или регистр.
Ну, он не возвращает переменную, но создает локальный объект MyT и сразу же возвращает его. Эти локальные объекты также создаются в стеке, не так ли? Какая тогда разница? @user17732522 в комментариях к вопросу написал про немедленную привязку, о которой я не в курсе. Это то, о чем вы тоже говорите?
Освежить ваши знания о RVO. В старых компиляторах вы увидите вызов конструктора копирования и ссылку на нелокальную копию.
Немедленная привязка означает привязку только к созданному объекту. Привязка к возвращаемому объекту не является непосредственной привязкой. Если временное привязывается сразу, все в порядке.
Я бы удалил части кода, не связанные с вопросом.