Eindeutiger Zeiger - Warum wird der Destruktor dreimal aufgerufen?

8

Ich habe eine Methode, die ein Objekt nach Wert zurückgibt. Die Methode stammt aus einer Bibliothek, über die ich keine Kontrolle habe. Für die weitere Bearbeitung des Objekts möchte ich weiterhin mit einem unique_ptr an diesem Objekt arbeiten. Hier ist ein Beispiel:

#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());
}

Das Beispiel erzeugt die folgende Ausgabe:

Constructor!
Destructor!
Destructor!
Destructor!

Warum wird der Zerstörer von Bla hier dreimal gerufen? Ist die Art und Weise, wie ich den unique_prt erstelle, korrekt?

Tobi S.
quelle
4
Ich schlage vor, Sie verwenden einen Debugger, um einen Haltepunkt im Destruktor festzulegen. Dann können Sie leicht sehen, von wo aus die Destruktoren aufgerufen werden.
Ein Programmierer
3
Hinweis: std::movebewegt nichts. Es wird nur von einem Typ zum anderen geworfen.
Abhiarora
4
Definieren Sie Kopier- und Verschiebungskonstruktoren, um das vollständige Bild anzuzeigen. Auch die Verwendung von std::moveon returnist ein großer Fehler.
Marek R
6
Geben Sie kein Objekt mit std :: move zurück. Dies verhindert RVO.
Daniel Schlößer

Antworten:

11

Es gibt tatsächlich 3 Mal, dass eine Instanz von erstellt Blawird.

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

Kehre nicht durch Bewegung zurück. blaKehre einfach zurück , in den meisten Fällen wird die Kopie entfernt.

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

Beachten Sie, dass make_unique<Bla>immer eine neue Instanz erstellt wird. In diesem Fall wird die Kopie erstellt, da Sie eine andere Instanz übergeben.

Ein Hinweis darauf, dass eine Kopierkonstruktion stattfindet, ist, dass Ihr Standardkonstruktor nur einmal aufgerufen wird, während der Destruktor dreimal aufgerufen wird. Dies liegt daran, dass in den beiden anderen Fällen der implizite Kopier- (oder Verschiebungs-) Konstruktor aufgerufen wird ( Bla::Bla(Bla const&)).

Rustyx
quelle
Vielen Dank für die Erklärung, ich dachte, dass std :: move das Kopieren verhindern sollte, aber anscheinend macht es das Gegenteil.
Tobi S.
1
@TobiS. Es wäre eine Verschiebungskonstruktion anstelle einer Kopierkonstruktion, aber Ihr Objekt verfügt nicht automatisch über einen Verschiebungskonstruktor, da Sie einen Destruktor definiert haben .
user253751
@ user253751: Danke für die Klarstellung.
Tobi S.
Denkst du // 2nd construction (return by copy)nicht richtig? Der zurückgegebene Wert wird verschiebungskonstruiert? Siehe dies (ich könnte mich irren): stackoverflow.com/a/60487169/5735010 .
Abhiarora
1
@abhiarora siehe 2. Kommentar. Es gibt keinen Verschiebungskonstruktor.
Rustyx
3

Der Compiler kann Sie sogar davor warnen

Das Verschieben eines lokalen Objekts in einer return-Anweisung verhindert die Elision von Kopien.

Ich bin nicht 100% sicher, aber ich denke, Sie erhalten die drei Desctructor-Anrufe von:

  • Die lokale Variable blavonGetBla()
  • Der Rückgabewert von GetBla()nach seiner Verwendung instd::make_unique<Bla>(GetBla());
  • Offensichtlich vom Zerstörer der std::unique_ptr

Am einfachsten ist es, std::make_uniqeden Standardkonstruktor aufrufen zu lassen 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>();
}

Ausgabe

Constructor!
Destructor!
churill
quelle
2

Der richtige Weg zu erstellen unique_ptr:

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

Ihr Code erstellt jedoch drei Instanzen von Bla:

  1. Lokales Objekt blain GetBla()Funktion.
  2. Rückgabewert von GetBla().
  3. Schließlich make_unique()wird eine weitere Instanz erstellt.

HINWEIS:

  1. Bei Vorhandensein eines benutzerdefinierten Destruktors generiert der Compiler keinen Verschiebungskonstruktor, sodass der GetBla()Rückgabewert eine Kopie des lokalen Objekts ist bla.
  2. Da GetBla()kehrt move‚lokales Objekt ed, copy-elision unterdrückt wird.
Igor R.
quelle
1

Um wirklich zu sehen, was hinter den Kulissen passiert, können Sie entweder einen Debugger verwenden oder einen Kopierkonstruktor definieren . Ich habe den Kopierkonstruktor in Ihren Code eingefügt. Probieren Sie den folgenden Code aus:

#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;
} 

HINWEIS:

std::movebewegt nichts. Es wirft nur auslvalue Referenz zu rvalueReferenz umgewandelt, und Ihr zurückgegebenes Objekt könnte über einen Verschiebungskonstruktor erstellt worden sein (und die Kopierelision könnte unterdrückt werden), aber der Compiler hat den moveKonstruktor nicht implizit deklariert , da Sie den Destruktor definiert haben (und ich habe einen Kopierkonstruktor hinzugefügt) in meinem Beispiel)

Ausgänge:

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

Siehe meine Kommentare unten:

  1. Das Objekt blaist in Funktion aufgebautGetBla() über den Standardkonstruktor erstellt.
  2. Der Rückgabewert von GetBla() Funktion wird aus dem in erstellten Objekt kopiert # 1.
  3. bla Objekt (in # 1 konstruiert) wird zerstört und sein Destruktor wird aufgerufen.
  4. std::make_unique<Bla> Anrufe new und dann den entsprechenden Konstruktor auf und wählt das auscopy Konstruktor aus.
  5. Das in # 2 erstellte Objekt wird zerstört.
  6. Schließlich wird das in # 4 erstellte Objekt zerstört.
abhiarora
quelle