Уникальный указатель - почему деструктор вызывается 3 раза - PullRequest
8 голосов
/ 02 марта 2020

У меня есть метод, который возвращает объект по значению. Метод исходит из библиотеки, которую я не могу контролировать. Для дальнейшей обработки объекта я хочу продолжить работу с unique_ptr над этим объектом. Вот пример:

#include <iostream>
#include <memory>

class Bla {
  public:
      Bla() { std::cout << "Constructor!\n"; }
      ~Bla() { std::cout << "Destructor!\n"; }
};

Bla GetBla() {
  Bla bla;
  return std::move(bla);
}

int main() {
  auto bla = std::make_unique<Bla>(GetBla());
}

Пример дает следующий вывод:

Constructor!
Destructor!
Destructor!
Destructor!

Почему деструктор Бла вызывается здесь 3 раза? Правильный ли способ создания unique_prt?

Ответы [ 4 ]

11 голосов
/ 02 марта 2020

Действительно, 3 раза создается экземпляр Bla.

Bla GetBla() {
  Bla bla;    // 1st construction
  return std::move(bla); // 2nd construction (return by copy)
}

Не возвращаться на ходу. Просто верните bla, в большинстве случаев копия будет удалена.

  auto bla = std::make_unique<Bla>(GetBla());  // 3rd construction - Bla copy construction

Обратите внимание, что make_unique<Bla> всегда создает новый экземпляр. В этом случае, поскольку вы передаете другой экземпляр, он становится копируемым.

Намек на то, что происходит копирование, заключается в том, что ваш конструктор по умолчанию вызывается только один раз, а деструктор вызывается 3 раза. Это связано с тем, что в двух других случаях вызывается неявный конструктор копирования (или перемещения) (Bla::Bla(Bla const&)).

3 голосов
/ 02 марта 2020

Компилятор может даже предупредить вас, что

перемещение локального объекта в операторе возврата предотвращает копирование.

Я не уверен на 100%, но я Я думаю, что вы получаете три вызова desctructor:

  • Локальная переменная bla из GetBla()
  • Возвращаемое значение из GetBla() после его использования в std::make_unique<Bla>(GetBla());
  • Очевидно, от деструктора std::unique_ptr

Самый простой способ - позволить std::make_uniqe вызвать конструктор по умолчанию Bla:

auto bla = std::make_unique<Bla>(); // Calls Bla::Bla() to initalize the owned object
#include <iostream>
#include <memory>

class Bla {
  public:
      Bla() { std::cout << "Constructor!\n"; }
      ~Bla() { std::cout << "Destructor!\n"; }
};

int main() {
  auto bla = std::make_unique<Bla>();
}

Вывод

Constructor!
Destructor!
2 голосов
/ 02 марта 2020

Правильный способ создания unique_ptr:

auto bla = std::make_unique<Bla>();

Однако ваш код создает 3 экземпляра Bla:

  1. Локальный объект bla в GetBla() function.
  2. Возвращаемое значение GetBla().
  3. Наконец, make_unique() создает еще один экземпляр.

ПРИМЕЧАНИЕ:

  1. При наличии пользовательского деструктора компилятор не генерирует move-constructor, поэтому возвращаемое значение GetBla() является копией локального объекта bla.
  2. , поскольку GetBla() возвращает move 'ed локальный объект, copy-elision подавлен.
1 голос
/ 02 марта 2020

Чтобы действительно увидеть, что происходит за кулисами, вы можете использовать отладчик или определить конструктор copy . Я добавил конструктор копирования в ваш код. Попробуйте код, приведенный ниже:

#include <iostream>
#include <memory>

class Bla {
public:
    Bla(void) 
    {
        std::cout << "Constructor!" << std::endl;
    }
    //Bla(Bla &&)
    //{
    //    std::cout << "Move Constructors" << std::endl;
    //}
    Bla(const Bla &)
    {
        std::cout << "Copy Constructors" << std::endl;
    }
    ~Bla(void)
    {
        std::cout << "Destructor!" << std::endl;
    }
private:
    int a = 2;
};

Bla GetBla(void) 
{
    Bla bla; // Default Constructor Called here
    return std::move(bla); // Second Construction over here
}

int main(void)
{
    auto bla = std::make_unique<Bla>(GetBla()); // Third Construction
    return 0;
} 

ПРИМЕЧАНИЕ:

std::move ничего не двигает. Он просто преобразует ссылку lvalue в ссылку rvalue, и ваш возвращенный объект мог быть создан с помощью конструктора перемещения (и copy elision может быть подавлено), но компилятор не объявляет неявно move конструктор потому что вы определили деструктор (и я добавил конструктор копирования в моем примере)

Выходы:

Constructor! # 1
Copy Constructors # 2
Destructor! # 3
Copy Constructors # 4
Destructor! # 5
Destructor! # 6

См. мои комментарии ниже:

  1. Объект bla создается в функции GetBla() через конструктор по умолчанию.
  2. Возвращаемое значение функции GetBla() создается методом копирования из объекта, созданного в # 1.
  3. bla объект (построенный в # 1) разрушается и вызывается его деструктор.
  4. std::make_unique<Bla> вызывает new, а затем вызывает соответствующий конструктор и выбирает конструктор copy.
  5. Объект, созданный в # 2, уничтожается.
  6. Наконец объект, созданный в # 4, уничтожен.
...