Почему при вызове func2() деструктор для "a" вызывается при завершении функции, а при вызове func() при при завершении main?
class A{
public:
~A(){
std::cout << "~" << std::endl;
std::cout << *n << std::endl;
std::cout << "END of " << n << std::endl;
delete n;
}
int* n;
};
A func(){
A a;
a.n = new int(5);
return a;
};
int* func2(){
A a;
a.n = new int(3);
return a.n;
}
int main() {
auto funcRes = func();
auto func2Res = func2();
std::cout << "func().n = " << *funcRes.n << std::endl;
std::cout << "func2() = " << *func2Res << std::endl;
}
Вывод на консоль:
~
3
END of 0xf4fc40
func().n = 5
func2() = 0
~
5
END of 0xf4fc20
Ответ
Давайте для начала разберём вариант с func2(). Внутри func2() создается локальный объект типа A, который как и любой локальный объект живёт только внутри блока (области видимости), в котором он определён. После выхода из этой области видимости будет вызван деструктор и объект будет уничтожен. В частности мы внутри деструктора освободим память, которая выделилась под целочисленную переменную, на которую указывает член n. Таким образом внутри main при разименовывании *func2Res мы будем обращаться к памяти, которая нам уже не принадлежит.
Что же происходит при вызове функции func?
На самом деле вместо того, чтобы создавать новый объект внутри main с помощью конструктора копирования будет использован тот объект, который был создан внутри функции func. Эта оптимизация называется NRVO (Named return value optimization). Немножко подробнее:
RVO(return value optimization) позволяет компилятору оптимизировать следующие конструкции:
class A{};
A func() {
return A(); // будет вызван конструктор
}
int main() {
A a = func(); // конструктор вызван не будет, так как сработала оптимизация
}
NRVO расширяет возможности оптимизации и позволяет компилятору оптимизировать не только возвращение объектов созданных непосредственно при вызове return, но и разных именованных значений, которые былии объявлены в этой функции раньше:
class A{};
A func() {
A a; // конструктор будет вызван только тут
return a;
}
int main() {
A a = func() // конструктор копирования не будет вызван. сработает NRVO
}
Что касается стандарта, то он неь гарантирует, что компилятор должен оптимизировать подобные выражения, но разрешает иметь им такое поведение. Смотреть 12.8.*
Подробнее:
1. Named Return Value Optimization in Visual C++
2. RVO и NRVO
Комментариев нет:
Отправить комментарий