У меня есть некоторые трудности с пониманием того, что действительно делается после возврата значений в C ++.
Давайте иметь следующий код:
class MyClass {
public:
int id;
MyClass(int id) {
this->id = id;
cout << "[" << id << "] MyClass::ctor\n";
}
MyClass(const MyClass& other) {
cout << "[" << id << "] MyClass::ctor&\n";
}
~MyClass() {
cout << "[" << id << "] MyClass::dtor\n";
}
MyClass& operator=(const MyClass& r) {
cout << "[" << id << "] MyClass::operator=\n";
return *this;
}
};
MyClass foo() {
MyClass c(111);
return c;
}
MyClass& bar() {
MyClass c(222);
return c;
}
MyClass* baz() {
MyClass* c = new MyClass(333);
return c;
}
Я использую gcc 4.7.3.
Случай 1
Когда я звоню:
MyClass c1 = foo();
cout << c1.id << endl;
Выход:
[111] MyClass::ctor
111
[111] MyClass::dtor
Я понимаю, что в foo
Объект создается в стеке, а затем уничтожается при операторе return, потому что это конец области видимости. Возврат осуществляется путем копирования объекта (конструктор копирования), который впоследствии присваивается c1
в основном (оператор присваивания). Если я прав, почему нет вывода из конструктора копирования или оператора присваивания? Это из-за RVO?
Дело 2
Когда я звоню:
MyClass c2 = bar();
cout << c2.id << endl;
Выход:
[222] MyClass::ctor
[222] MyClass::dtor
[4197488] MyClass::ctor&
4197488
[4197488] MyClass::dtor
Что здесь происходит? Я создаю переменную, затем возвращаю ее, и переменная уничтожается, потому что это конец области видимости. Компилятор пытается скопировать эту переменную с помощью конструктора копирования, но она уже уничтожена, и поэтому у меня есть случайное значение? Так что же на самом деле в c2
в основном?
Дело 3
Когда я звоню:
MyClass* c3 = baz();
cout << c3->id << endl;
Выход:
[333] MyClass::ctor
333
Это самый простой случай? Я возвращаю динамически созданный указатель, который лежит в куче, поэтому память выделяется и не освобождается автоматически. Это тот случай, когда деструктор не вызывается и у меня возникает утечка памяти. Я прав?
Есть ли другие случаи или вещи, которые не очевидны, и я должен знать, чтобы полностью освоить возвращаемые значения в C ++? 😉 Каков рекомендуемый способ вернуть объект из функции (если таковой имеется) — есть ли какие-то правила?
Случай 1:
MyClass foo() {
MyClass c(111);
return c;
}
...
MyClass c1 = foo();
типичный случай, когда может быть применено RVO. Это называется копия инициализация и оператор присваивания не используется, поскольку объект создается на месте, в отличие от ситуации:
MyClass c1;
c1 = foo();
где c1
построен, временный c
в foo()
построен, [копия c
построен], c
или копия c
назначен на c1
, [ копия c
разрушен] и c
разрушен. (что именно происходит, зависит от того, удаляет ли компилятор избыточную копию c
создается или нет).
Дело 2:
MyClass& bar() {
MyClass c(222);
return c;
}
...
MyClass c2 = bar();
Запускает неопределенное поведение так как вы возвращаете ссылку на локальную (временную) переменную c
~ объект с автоматической продолжительностью хранения.
Дело 3:
MyClass* baz() {
MyClass* c = new MyClass(333);
return c;
}
...
MyClass c2 = bar();
является самым простым, так как Вы контролируете, что происходит но с очень неприятными последствиями: вы несете ответственность за управление памятью, Именно поэтому вам следует избегать динамического размещения такого рода всегда, когда это возможно (и предпочитайте вариант 1).
Позвольте мне просто добавить, что случай № 2 является одним из случаев неопределенного поведения в языке C ++, поскольку возвращение ссылки на локальную переменную недопустимо. Это потому, что локальная переменная имеет точно определенное время жизни, и, возвращая ее по ссылке, вы возвращаете ссылку на переменную, которая больше не существует, когда функция возвращается. Таким образом, вы проявляете неопределенное поведение, и значение данной переменной является практически случайным. Как результат остальной части вашей программы, так как Может случиться что угодно.
Большинство компиляторов выдают предупреждение, когда вы пытаетесь сделать что-то вроде этого (либо вернуть локальную переменную по ссылке, либо по адресу) — например, gcc сообщает мне что-то вроде этого:
bla.cpp:37:13: warning: reference to local variable ‘c’ returned [-Wreturn-local-addr]
Однако вы должны помнить, что компилятору вообще не требуется выдавать какие-либо предупреждения, когда возникает оператор, который может проявлять неопределенное поведение. Однако таких ситуаций, как эта, следует избегать любой ценой, потому что они практически никогда не бывают правильными.
1) да.
2) У вас есть случайное значение, потому что ваша копия не может и operator=
не копируйте значение id
, Тем не менее, вы правы, предполагая, что нет никакого значения для объекта после того, как он был удален.
3) Да.