一意のポインタ-デストラクタが3回呼び出されるのはなぜですか


8

値によってオブジェクトを返すメソッドがあります。このメソッドは、私が制御できないライブラリからのものです。オブジェクトをさらに処理するために、このオブジェクトで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
デバッガを使用して、デストラクタにブレークポイントを設定することをお勧めします。その後、デストラクタが呼び出された場所から簡単に確認できます。
プログラマ

3
ヒント:std::move何も動かしません。あるタイプから別のタイプにキャストするだけです。
abhiarora

4
全体像を見るために、コピーおよび移動コンストラクターを定義します。また、std::moveonの使用returnは大きな間違いです。
Marek R

6
std :: moveを使用してオブジェクトを返さないでください。これはRVOを防ぎます。
DanielSchlößer

回答:


11

のインスタンスBlaが構築される回数は実際に3回あります。

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>常に新しいインスタンスを構築することに注意してください。この場合、別のインスタンスを渡すため、コピー構築になります。

コピー構築が行われるヒントは、デフォルトのコンストラクターが1回だけ呼び出され、デストラクターが3回呼び出されることです。他の2つのケースでは、暗黙的なコピー(または移動)コンストラクターが呼び出されるためです(Bla::Bla(Bla const&))。


説明をありがとう、私はstd :: moveがコピーを防ぐことになっていると思っていましたが、明らかにそれは逆です。
トビS.

1
@TobiS。これはコピー構築ではなく移動構築になりますが、デストラクタを定義したため、オブジェクトには自動的に移動コンストラクタがありません。
user253751

@ user253751:説明をありがとう。
トビS.

あなた// 2nd construction (return by copy)は正しくないと思いますか?戻り値は移動構築されますか?これを見てください(私は間違っている可能性があります):stackoverflow.com/a/60487169/5735010
abhiarora

1
@abhiarora 2番目のコメントを参照してください。移動コンストラクタはありません。
rustyx

3

コンパイラはあなたに警告さえするかもしれません

returnステートメントでローカルオブジェクトを移動すると、コピーが省略されなくなります。

100%確実ではありませんが、次の3つのデストラクター呼び出しを取得すると思います。

  • 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

作成する正しい方法unique_ptr

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

ただし、コードは次の3つのインスタンスを作成しますBla

  1. 関数bla内のローカルオブジェクトGetBla()
  2. の戻り値GetBla()
  3. 最後に、make_unique()もう1つのインスタンスを作成します。

注意:

  1. ユーザー定義のデストラクタがある場合、コンパイラはmove-constructorを生成しないため、GetBla()戻り値はローカルオブジェクトのコピーですbla
  2. GetBla()moveローカルオブジェクトを返したので、コピー削除は抑制されます。

1

背後で何が起こっているかを実際に確認するには、デバッガーを使用するか、コピーコンストラクターを定義します。コードにコピーコンストラクターを追加しました。以下のコードを試してください:

#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参照して、返されるオブジェクトは、コンストラクタ(と移動を経て構築されている可能性がコピーの省略を抑制することができる)が、コンパイラが暗黙的に宣言されていませんmoveコンストラクタあなたはデストラクタを定義している(&Iは、コピーコンストラクタを追加したので、私の例では)

出力:

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で作成されたオブジェクトが破棄されます。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.