Was sind die Vorteile von boost :: noncopyable?

70

Um das Kopieren einer Klasse zu verhindern, können Sie ganz einfach einen Konstruktor / Zuweisungsoperator für private Kopien deklarieren. Sie können aber auch erben boost::noncopyable.

Was sind in diesem Fall die Vor- und Nachteile der Verwendung von Boost?

zehn vier
quelle
18
Beachten Sie, dass Sie in C ++ 11 schreiben würdenstruct Foo{Foo(const Foo&)=delete;};
spraff
2
Ich nehme an, es liegt hauptsächlich daran, dass der durchschnittliche Peon nicht versteht, warum Ihr Kopierkonstruktor privat und undefiniert ist.
Oscar Korz
4
@spraff Ich glaube du würdest auch brauchen Foo & operator=(const Foo &) = delete;?
wjl
Ja. Es war ein Beispiel, keine vollständige Implementierung.
Spraff

Antworten:

52

Ich sehe keinen Dokumentationsvorteil:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

vs:

struct A
{
     A(const A&) = delete;
     A& operator=(const A&) = delete;
};

Wenn Sie Nur-Verschieben-Typen hinzufügen, sehe ich die Dokumentation sogar als irreführend an. Die folgenden zwei Beispiele sind nicht kopierbar, obwohl sie beweglich sind:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
    A(A&&) = default;
    A& operator=(A&&) = default;
};

vs:

struct A
{
    A(A&&) = default;
    A& operator=(A&&) = default;
};

Bei Mehrfachvererbung kann es sogar zu einer Platzstrafe kommen:

#include <boost/noncopyable.hpp>

struct A
    : private boost::noncopyable
{
};

struct B
    : public A
{
    B();
    B(const B&);
    B& operator=(const B&);
};

struct C
    : public A
{
};

struct D
    : public B,
      public C,
      private boost::noncopyable
{
};

#include <iostream>

int main()
{
    std::cout << sizeof(D) << '\n';
}

Für mich druckt dies aus:

3

Aber das, von dem ich glaube, dass es eine überlegene Dokumentation hat:

struct A
{
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

struct B
    : public A
{
    B();
    B(const B&);
    B& operator=(const B&);
};

struct C
    : public A
{
    C(const C&) = delete;
    C& operator=(const C&) = delete;
};

struct D
    : public B,
      public C
{
    D(const D&) = delete;
    D& operator=(const D&) = delete;
};

#include <iostream>

int main()
{
    std::cout << sizeof(D) << '\n';
}

Ausgänge:

2

Ich finde es viel einfacher, meine Kopiervorgänge zu deklarieren, als zu überlegen, ob ich boost::non_copyablemehrmals davon abgeleitet bin oder nicht und ob mich das kosten wird. Vor allem, wenn ich nicht der Autor der vollständigen Vererbungshierarchie bin.

Howard Hinnant
quelle
25
Um fair zu sein, boost::noncopyablewar lange vor C ++ 11 verfügbar und kompilierte Unterstützung für = delete. Ich stimme Ihnen zu, dass C ++ 11-kompatible Compiler mittlerweile veraltet sind.
Matthieu M.
6
Jemand hatte eine gute Idee und machte die noncopyableCRTP-Basisklasse so, dass alle Basisklassen in der Hierarchie eindeutig sind.
Johannes Schaub - litb
3
Ein weiterer Nachteil ist, dass private: __copy_constructor__;es vollständig portabel ist und Sie keine ~ 40 MB Boost-Abhängigkeiten benötigen.
Luis Machuca
2
Dies wirft die Frage auf: Was in Boost sonst noch durch C ++ 11 überholt ist?
Jon
2
@ Jon: Es gibt keine festen Antworten auf diese Frage. (Nur als Beispiel) würde ich jedoch in Betracht ziehen, std::vector<std::unique_ptr<animal>>bevor ich nach boost::ptr_vector<animal>( boost.org/doc/libs/1_54_0/libs/ptr_container/doc/tutorial.html ) greife . Begründung: Wenn ich weiß vectorund ich weiß unique_ptr, dann kenne ich die Semantik von Vektoren von unique_ptr. Und ich weiß, wie die std :: -Algorithmen (zB sort) damit interagieren. Ich muss nicht alles über einen neuen Container mit seinen Mitgliedsalgorithmen lernen (z. B. Mitgliedersortierung).
Howard Hinnant
43

Es macht die Absicht explizit und klar , andernfalls muss man die Definition der Klasse sehen und nach der Deklaration suchen, die sich auf die Kopiersemantik bezieht, und dann nach dem Zugriffsspezifizierer suchen, in dem sie deklariert ist , um festzustellen, ob die Klasse ist nicht kopierbar oder nicht. Eine andere Möglichkeit, dies zu erkennen, indem Sie Code schreiben, für den eine kopiersemantische Aktivierung erforderlich ist, und den Kompilierungsfehler anzeigen.

Nawaz
quelle
4
Sie müssen die Definition nicht sehen, um zu sehen, dass ein Kopieroperator in der Deklaration privat ist.
Spraff
7
@spraff: Das nennt man Definition der Klasse. Eine Definition der Klasse enthält alle deklarierten Mitglieder.
Nawaz
1
Ein Teil des Vorteils der expliziten Darstellung besteht darin, dass die Bedeutung jetzt in die Metadaten des Typnamens eingebettet ist. Jetzt können Sie eine Funktion schreiben, die beispielsweise nur nicht kopierbare Objekte akzeptiert.
vierundvierzig
3
Wenn Sie keinen Zugriff auf eine Klassendefinition haben, handelt es sich um einen unvollständigen Typ, den Sie für nichts wirklich verwenden können . Ohne diese Definition können Sie nicht sehen, dass es auch erbt noncopyable. Es ist also ein strittiger Punkt.
Spraff
5
@spraff: Ich verstehe nicht, was du mit technischem Unterschied meinst . Habe ich so etwas gesagt?
Nawaz
42

Zusammenfassend, was andere gesagt haben:

Vorteile boost::noncopyablegegenüber privaten Kopiermethoden :

  1. Es ist expliziter und beschreibender in der Absicht. Die Verwendung von Funktionen für private Kopien ist eine Redewendung, deren Erkennung länger dauert als noncopyable.
  2. Es ist weniger Code / weniger Eingabe / weniger Unordnung / weniger Raum für Fehler (am einfachsten wäre es, versehentlich eine Implementierung bereitzustellen).
  3. Es bettet die Bedeutung direkt in die Metadaten des Typs ein, ähnlich einem C # -Attribut. Sie können jetzt eine Funktion schreiben, die nur Objekte akzeptiert, die nicht kopierbar sind.
  4. Möglicherweise werden Fehler früher im Erstellungsprozess abgefangen. Der Fehler wird eher zur Kompilierungszeit als zur Verbindungszeit angezeigt, falls die Klasse selbst oder Freunde der Klasse das fehlerhafte Kopieren durchführen.
  5. (fast das gleiche wie # 4) Verhindert, dass die Klasse selbst oder Freunde der Klasse die Methoden für private Kopien aufrufen.

Vorteile privater Kopiermethoden gegenüberboost::noncopyable :

  1. Keine Boost-Abhängigkeit
zehn vier
quelle
10
Es gibt auch einen Platznachteil, wie von @Howard Hinnant
Philip hervorgehoben
16
  1. Die Absicht von boost :: noncopyable ist klarer.
  2. Boost :: noncopyable verhindert, dass die Klassenmethoden versehentlich den Konstruktor für private Kopien verwenden.
  3. Weniger Code mit boost :: noncopyable.
Thiton
quelle
16

Ich kann nicht verstehen, warum es sonst niemand zu erwähnen scheint, aber:

Mit noncopyable Ihnen schreiben Sie den Namen Ihrer Klasse nur einmal.

Ohne fünffache Vervielfältigung : Ein A für 'Klasse A', zwei zum Deaktivieren der Zuweisung und zwei zum Deaktivieren des Kopierkonstruktors.

ansgri
quelle
1
und Sie sagen, es ist nicht kopierbar, was die Lesbarkeit erhöht und gesucht werden kann.
hochl
10

Zitieren der Dokumentation:

"Der traditionelle Weg, um damit umzugehen, besteht darin, einen Konstruktor für private Kopien und eine Kopierzuweisung zu deklarieren und dann zu dokumentieren, warum dies getan wird. Die Ableitung von nicht kopierbar ist jedoch einfacher und klarer und erfordert keine zusätzliche Dokumentation."

http://www.boost.org/libs/utility/utility.htm#Class_noncopyable

Viktor Sehr
quelle
8

Ein konkreter Vorteil (abgesehen davon, dass Sie Ihre Absicht etwas klarer ausdrücken) ist, dass der Fehler früher erkannt wird, in der Kompilierungsphase und nicht in der Verknüpfungsphase, wenn eine Mitglied- oder Freundfunktion versucht, ein Objekt zu kopieren. Auf den Konstruktor / die Zuweisung der Basisklasse kann nirgendwo zugegriffen werden, was zu einem Kompilierungsfehler führt.

Es verhindert auch die Funktionen versehentlich definieren (dh Typisierung {}statt ;), einen kleinen Fehler , die auch unbemerkt bleiben können, aber die würden dann Mitglieder und Freunde , damit ungültige Kopien des Objekts machen.

Mike Seymour
quelle
Das habe ich gesucht;)
vierundvierzig
@ Mike : ...is that the error will be caught sooner, at the compile stage not the link stage. Wie genau? Tut sogar boost::noncopyabledas Gleiche, was Sie tun würden, wenn Sie es nicht verwenden.
Nawaz
@Nawaz: Wenn Sie keine noncopyableBasisklasse verwenden, deklarieren Sie einen privaten Konstruktor in Ihrer Klasse. Der Zugriff ist für die Mitglieder und Freunde der Klasse zugänglich, sodass kein Kompilierungsfehler vorliegt - nur ein Linkfehler aufgrund der fehlenden Definition. (Sofern Sie nicht versehentlich eine Definition angeben - die Verwendung einer Basisklasse verhindert auch diesen Fehler).
Mike Seymour
1
Da noncopyable über private Kopierfunktionen verfügt , kann die untergeordnete Klasse überhaupt nicht auf sie zugreifen - daher ein Compilerfehler. Wenn Sie die Funktionen in die untergeordnete Klasse einfügen, kann auf sie zugegriffen werden. Sie sind daher gültig, bis der Linker feststellt, dass sie nicht definiert sind.
Tenfour
@ MikeSeymour: In Ordnung. Es geht nur um Mitglieder und Freunde. Ich habe nicht an sie gedacht. Schöner Punkt. Aus praktischer Sicht ist dies jedoch fast kein Vorteil, da die moderne IDE oder der sogenannte Compiler beide nacheinander ausführen, was bedeutet, dass Sie nur Fehler erhalten.
Nawaz
3

Der Vorteil ist, dass Sie keinen privaten Kopierkonstruktor und keinen privaten Kopieroperator selbst schreiben müssen und dies Ihre Absicht klar zum Ausdruck bringt, ohne zusätzliche Dokumentation zu schreiben.

Nikko
quelle
3

Ein kleiner Nachteil (GCC-spezifisch) ist, dass, wenn Sie Ihr Programm mit kompilieren g++ -Weffc++und Klassen haben, die Zeiger enthalten, z

class C : boost::noncopyable
{
public:
  C() : p(nullptr) {}

private:
  int *p;
};

GCC versteht nicht, was passiert:

Warnung: 'Klasse C' hat Zeigerdatenelemente [-Weffc ++]
Warnung: überschreibt jedoch nicht 'C (const S &)' [-Weffc ++]
Warnung: oder 'operator = (const C &)' [-Weffc ++]

Während es sich nicht beschweren wird mit:

#define DISALLOW_COPY_AND_ASSIGN(Class) \
  Class(const Class &) = delete;     \
  Class &operator=(const Class &) = delete

class C
{
public:
  C() : p(nullptr) {}
  DISALLOW_COPY_AND_ASSIGN(C);

private:
  int *p;
};

PS Ich weiß, dass GCCs -Weffc ++ mehrere Probleme hat. Der Code, der nach "Problemen" sucht, ist sowieso ziemlich simpel ... manchmal hilft es.

Manlio
quelle
2

Ich würde lieber boost :: noncopyable verwenden, als den Kopierkonstruktor und den Zuweisungsoperator manuell zu löschen oder zu privatisieren.

Ich benutze es jedoch fast nie entweder Methoden, weil:

Wenn ich ein nicht kopierbares Objekt erstelle, muss es einen Grund geben, warum es nicht kopierbar ist. Dieser Grund liegt in 99% der Fälle darin, dass ich Mitglieder habe, die nicht sinnvoll kopiert werden können. Möglicherweise eignen sich solche Mitglieder auch besser als Details zur privaten Implementierung. Also mache ich die meisten solcher Klassen so:

struct Whatever {
  Whatever();
  ~Whatever();
  private:
  struct Detail;
  std::unique_ptr<Detail> detail;
};

Jetzt habe ich eine private Implementierungsstruktur und da ich std :: unique_ptr verwendet habe, kann meine Klasse der obersten Ebene nicht kostenlos kopiert werden. Die daraus resultierenden Linkfehler sind verständlich, da sie darüber sprechen, wie Sie ein std :: unique_ptr nicht kopieren können. Für mich sind dies alle Vorteile von boost :: noncopyable und einer privaten Implementierung in einem.

Der Vorteil dieses Musters besteht später darin, dass ich, wenn ich beschließe, meine Objekte dieser Klasse tatsächlich kopierbar zu machen, einfach einen Kopierkonstruktor und / oder einen Zuweisungsoperator hinzufügen und implementieren kann, ohne die Klassenhierarchie zu ändern.

wjl
quelle
unique_ptr gibt den Eindruck, dass das Detail null du sein kann.
Viktor Sehr
Es könnte eine Null sein, die unique_ptr nicht konnte? Zumindest der Zeiger mit Boost-Gültigkeitsbereich verfügt über einen leeren Konstruktor, der sich mit null befasst - keine Ahnung über std :: unique_ptr.
Agodinhost
1

Der Nachteil, laut Scott Meyers, ist der Name "nicht natürlich", wenn Sie einen Nachteil davon finden müssen.

H Xu
quelle