Frage

Ich lerne C++ und habe kürzlich (hier im Stapelüberlauf) etwas über das Copy-and-Swap-Idiom gelernt und habe ein paar Fragen dazu.Angenommen, ich habe die folgende Klasse, die eine Copy-and-Swap-Sprache verwendet, nur als Beispiel:

class Foo {
private:
  int * foo;
  int size;

public:
  Foo(size_t size) : size(size) { foo = new int[size](); }
  ~Foo(){delete foo;}

  Foo(Foo const& other){
    size = other.size;
    foo = new int[size];
    copy(other.foo, other.foo + size, foo);
  }

  void swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
  }

  Foo& operator=(Foo g) { 
    g.swap(*this); 
    return *this; 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

Meine Frage lautet: Angenommen, ich habe eine andere Klasse, die ein Foo-Objekt als Daten, aber keine Zeiger oder andere Ressourcen hat, die möglicherweise benutzerdefiniertes Kopieren oder Zuweisen erfordern:

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) {}; 
  Bar& operator=(Bar other) {bar = other.bar;}
};

Jetzt habe ich eine Reihe von Fragen:

  1. Sind die Methoden und Konstruktoren wie oben für die implementiert? Bar klassensicher?Nachdem ich das Copy-and-Swap-Verfahren verwendet habe Foo Stellen Sie sicher, dass beim Zuweisen oder Kopieren kein Schaden entstehen kann Bar?

  2. Ist die Übergabe des Arguments als Referenz im Kopierkonstruktor und im Swap obligatorisch?

  3. Ist es richtig, das zu sagen, wenn das Argument von operator= als Wert übergeben wird, wird der Kopierkonstruktor für dieses Argument aufgerufen, um eine temporäre Kopie des Objekts zu generieren, und es ist diese Kopie, mit der dann ausgetauscht wird *this?Wenn ich per Referenz weitergegeben habe operator= Ich hätte ein großes Problem, oder?

  4. Gibt es Situationen, in denen diese Redewendung keine vollständige Sicherheit beim Kopieren und Zuweisen bietet? Foo?

War es hilfreich?

Lösung

1 – Sind die oben für die Bar-Klasse implementierten Methoden und Konstruktoren sicher?Nachdem ich das Copy-and-Swap-Verfahren für Foo verwendet habe, stelle ich sicher, dass beim Zuweisen oder Kopieren von Bar kein Schaden angerichtet werden kann?

Bezüglich des Copy-Ctors:das ist immer sicher (alles oder nichts).Entweder wird alles abgeschlossen oder es wird eine Ausnahme ausgelöst (nichts).

Wenn Ihre Klasse nur aus einem Mitglied besteht (d. h.(auch keine Basisklassen), ist der Zuweisungsoperator genauso sicher wie der der Mitgliedsklasse.Wenn Sie mehr als ein Mitglied haben, lautet der Zuweisungsoperator nicht mehr „Alles oder Nichts“.Der Zuweisungsoperator des zweiten Mitglieds könnte eine Ausnahme auslösen, und in diesem Fall wird das Objekt „auf halbem Weg“ zugewiesen.Das bedeutet, dass Sie Copy-and-Swap in der neuen Klasse erneut implementieren müssen, um eine „Alles-oder-Nichts“-Zuweisung zu erhalten.

Es bleibt jedoch immer noch „sicher“ in dem Sinne, dass keine Ressourcen verloren gehen.Und natürlich wird der Zustand jedes Mitglieds einzeln konsistent sein – nur der Zustand der neuen Klasse wird nicht konsistent sein, weil ein Mitglied zugewiesen wurde und das andere nicht.

2 – Ist die Übergabe des Arguments als Referenz im Kopierkonstruktor und im Swap obligatorisch?

Ja, die Weitergabe als Referenz ist obligatorisch.Der Kopierkonstruktor ist derjenige, der Objekte kopiert kann nicht Nehmen Sie das Argument nach Wert, da dies bedeuten würde, dass das Argument kopiert werden muss.Dies würde zu einer unendlichen Rekursion führen.(Der Copy-Ctor würde für das Argument aufgerufen werden, was bedeuten würde, dass er den Copy-Ctor für das Argument aufrufen würde, was bedeuten würde ...).Für Swap ist der Grund ein anderer:Wenn Sie das Argument als Wert übergeben würden, könnten Sie Swap niemals verwenden, um den Inhalt zweier Objekte wirklich auszutauschen – das „Ziel“ des Swaps wäre eine Kopie des ursprünglich übergebenen Objekts, die sofort zerstört würde.

3 – Ist es richtig zu sagen, dass, wenn das Argument von „operator=“ als Wert übergeben wird, der Kopierkonstruktor für dieses Argument aufgerufen wird, um eine temporäre Kopie des Objekts zu generieren, und dass es diese Kopie ist, die dann mit *this ausgetauscht wird?Wenn ich in „operator=“ als Referenz übergeben würde, hätte ich ein großes Problem, oder?

Ja, das ist richtig.Allerdings ist es auch durchaus üblich, das Argument als Verweis auf eine Konstante zu verwenden, eine lokale Kopie zu erstellen und diese dann mit der lokalen Kopie auszutauschen.Der durch Referenz-zu-Konst Dieser Weg hat jedoch einige Nachteile (er deaktiviert einige Optimierungen).Wenn du bist nicht Wenn Sie jedoch Copy-and-Swap implementieren, sollten Sie wahrscheinlich eine Referenz auf eine Konstante verwenden.

4 – Gibt es Situationen, in denen diese Redewendung keine vollständige Sicherheit beim Kopieren und Zuweisen von Foo bietet?

Keine, die ich kenne.Natürlich kann es bei Multithreading immer zum Scheitern kommen (wenn nicht richtig synchronisiert), aber das sollte klar sein.

Andere Tipps

Sie sollten die Mitglieder Ihrer Klasse so weit wie möglich in der Initialisierungsliste initialisieren.Dadurch wird auch der Fehler behoben, von dem ich Ihnen im Kommentar berichtet habe.Vor diesem Hintergrund wird Ihr Code zu:

class Foo {
private:
  int size;
  int * foo;

public:
  Foo(size_t size) : size(size), foo(new int[size]) {}
  ~Foo(){delete[] foo;} // note operator delete[], not delete

  Foo(Foo const& other) : size(other.size), foo(new int[other.size]) {
    copy(other.foo, other.foo + size, foo);
  }

  Foo& swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
    return *this;
  }

  Foo& operator=(Foo g) { 
    return swap(g); 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

Und

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) { }
  Bar& swap(Bar &other) { bar.swap(other.bar); return *this; }
  Bar& operator=(Bar other) { return swap(other); }
}

das durchgehend die gleiche Redewendung verwendet

Notiz

wie in einem Kommentar zur Frage erwähnt, Bar's benutzerdefinierte Kopierkonstruktoren usw.sind unnötig, aber wir gehen davon aus Bar hat auch andere Dinge :-)

zweite Frage

Vorbei mit Verweis auf swap ist erforderlich, da beide Instanzen geändert werden.

Die Übergabe als Referenz an den Kopierkonstruktor ist erforderlich, da Sie bei der Übergabe als Wert den Kopierkonstruktor aufrufen müssten

dritte Frage

Ja

vierte Frage

Nein, aber es ist nicht immer die effizienteste Art, Dinge zu erledigen

Dies ist ein klassisches Beispiel dafür, dass das Befolgen einer Redewendung zu unnötigen Leistungseinbußen führt (vorzeitige Pessimisierung).Das ist nicht deine Schuld.Die Copy-and-Swap-Idiom ist viel zu hoch gehypt.Es Ist eine gute Redewendung.Aber es sollte nicht blind befolgt werden.

Notiz: Eines der teuersten Dinge, die Sie auf einem Computer tun können, ist das Zuweisen und Freigeben von Speicher.Es lohnt sich, dies zu vermeiden, wenn dies sinnvoll ist.

Notiz: In Ihrem Beispiel führt das Copy-and-Swap-Idiom immer eine Freigabe durch und häufig (wenn der rhs der Zuweisung ein L-Wert ist) auch eine Zuordnung.

Überwachung: Wann size() == rhs.size(), Es ist keine Freigabe oder Zuweisung erforderlich.Alles, was Sie tun müssen, ist a copy.Das ist viel, viel schneller.

Foo& operator=(const Foo& g) {
    if (size != g.size)
        Foo(g).swap(*this); 
    else
       copy(other.foo, other.foo + size, foo);
    return *this; 
}

D.h.Prüfen Sie zunächst, ob Sie Ressourcen recyceln können.Dann kopieren und austauschen (oder was auch immer), wenn Sie Ihre Ressourcen nicht recyceln können.

Notiz: Meine Kommentare stehen weder im Widerspruch zu den guten Antworten anderer noch zu etwaigen Problemen mit der Richtigkeit.In meinem Kommentar geht es nur um eine erhebliche Leistungseinbuße.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top