Страницы

Поиск по вопросам

суббота, 13 июля 2019 г.

Работа с динамической памятью, работа деструктора (NRVO)

Почему при вызове 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

Комментариев нет:

Отправить комментарий