Страницы

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

среда, 22 января 2020 г.

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

#cpp


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

    


Ответы

Ответ 1



Давайте для начала разберём вариант с 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

Ответ 2



Обратите внимание, что в вопросе вы все перепутали. func() инициализируется пятеркой, а на печать деструктор сначала выводит тройку. Значит, когда вызывается func() как раз деструктор не вызывается - он вызывается в конце main. Это потому, что работает RVO (оптимизация возвращаемого значения) - при возврате экземпляра объекта не происходит его пересоздания, при вызове func, A создается сразу внутри funcRes А в случае с func2(), где как раз фигурирует тройка, a живет только на время существования тела функции func2(); Отсюда порядок работы А(5) создалось сразу в funcRes Создалось A(3) Уничтожилось A(3) Уничтожилось funcRes(5)

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

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