Referenzelementvariablen als Klassenmitglieder

81

An meinem Arbeitsplatz sehe ich diesen Stil ausgiebig verwendet:

#include <iostream>

using namespace std;

class A
{
public:
   A(int& thing) : m_thing(thing) {}
   void printit() { cout << m_thing << endl; }

protected:
   const int& m_thing; //usually would be more complex object
};


int main(int argc, char* argv[])
{
   int myint = 5;
   A myA(myint);
   myA.printit();
   return 0;
}

Gibt es einen Namen, der diese Redewendung beschreibt? Ich gehe davon aus, dass der möglicherweise große Aufwand beim Kopieren eines großen komplexen Objekts vermieden werden soll.

Ist das im Allgemeinen eine gute Praxis? Gibt es Fallstricke bei diesem Ansatz?

Angus Comber
quelle
4
Eine mögliche Gefahr besteht darin, dass das Objekt, auf das Sie in der Mitgliedsvariablen verweisen, an anderer Stelle zerstört wird und Sie versuchen, über Ihre Klasse darauf zuzugreifen
Mathematiker

Antworten:

114

Gibt es einen Namen, der diese Redewendung beschreibt?

In UML heißt es Aggregation. Es unterscheidet sich von Zusammensetzung, dass das Mitgliedsobjekt nicht im Besitz des vorlegenden Klasse. In C ++ können Sie die Aggregation auf zwei verschiedene Arten implementieren, über Referenzen oder Zeiger.

Ich gehe davon aus, dass der möglicherweise große Aufwand beim Kopieren eines großen komplexen Objekts vermieden werden soll.

Nein, das wäre ein wirklich schlechter Grund, dies zu nutzen. Der Hauptgrund für die Aggregation ist, dass das enthaltene Objekt nicht dem enthaltenen Objekt gehört und daher deren Lebensdauer nicht gebunden ist. Insbesondere muss die Lebensdauer des referenzierten Objekts die des referenzierenden Objekts überleben. Es wurde möglicherweise viel früher erstellt und lebt möglicherweise über das Ende der Lebensdauer des Containers hinaus. Außerdem wird der Status des referenzierten Objekts nicht von der Klasse gesteuert, sondern kann sich extern ändern. Ist dies nicht constder Fall, kann die Klasse den Status eines Objekts ändern, das außerhalb des Objekts lebt.

Ist das im Allgemeinen eine gute Praxis? Gibt es Fallstricke bei diesem Ansatz?

Es ist ein Designwerkzeug. In einigen Fällen ist es eine gute Idee, in anderen nicht. Die häufigste Gefahr besteht darin, dass die Lebensdauer des Objekts, das die Referenz enthält, niemals die Lebensdauer des referenzierten Objekts überschreiten darf. Wenn das einschließende Objekt die Referenz verwendet, nachdem das referenzierte Objekt zerstört wurde, liegt ein undefiniertes Verhalten vor. Im Allgemeinen ist es besser, die Komposition der Aggregation vorzuziehen, aber wenn Sie sie brauchen, ist sie genauso gut wie jedes andere Werkzeug.

David Rodríguez - Dribeas
quelle
7
"Nein, das wäre ein wirklich schlechter Grund, dies zu nutzen". Könnten Sie bitte auf diesen Punkt näher eingehen? Was könnte stattdessen verwendet werden, um dies zu erreichen?
fällt am
@coincoin: Um was genau zu erreichen?
David Rodríguez - Dribeas
3
to prevent the possibly large overhead of copying a big complex object?
fällt am
3
@underscore_d danke für deine Antwort. Was passiert dann, wenn Sie keinen von beiden verwenden können? Stellen Sie sich vor, wir möchten dasselbe Objekt in verschiedenen Klassen teilen. Sie erhalten eine Kopie des Objekts für jede Klasse, wenn Sie dieses Mitgliedsobjekt als Wert übergeben? Die Lösung besteht also darin, intelligente Zeiger oder Referenzen zu verwenden, um ein Kopieren zu vermeiden. Nein ?
fällt
2
@ plats1 das habe ich gerade geschrieben, das weiß ich. Mein Punkt ist, dass Sie entweder intelligente Zeiger oder Referenzen verwenden können.
fällt am
33

Es wird Abhängigkeitsinjektion über Konstruktorinjektion genannt : Klasse ruft Adie Abhängigkeit als Argument für ihren Konstruktor ab und speichert den Verweis auf abhängige Klasse als private Variable.

Es gibt eine interessante Einführung auf Wikipedia .

Aus Gründen der Konstanz würde ich schreiben:

using T = int;

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  // ...

private:
   const T &m_thing;
};

Ein Problem mit dieser Klasse ist jedoch, dass sie Verweise auf temporäre Objekte akzeptiert:

T t;
A a1{t};    // this is ok, but...

A a2{T()};  // ... this is BAD.

Es ist besser hinzuzufügen (erfordert mindestens C ++ 11):

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  A(const T &&) = delete;  // prevents rvalue binding
  // ...

private:
  const T &m_thing;
};

Wie auch immer, wenn Sie den Konstruktor ändern:

class A
{
public:
  A(const T *thing) : m_thing(*thing) { assert(thing); }
  // ...

private:
   const T &m_thing;
};

Es ist so gut wie garantiert, dass Sie keinen Zeiger auf eine temporäre haben .

Da der Konstruktor einen Zeiger verwendet, ist es für Benutzer klarer, Adass sie auf die Lebensdauer des übergebenen Objekts achten müssen.


Etwas verwandte Themen sind:

Manlio
quelle
20

Gibt es einen Namen, der diese Redewendung beschreibt?

Es gibt keinen Namen für diese Verwendung, sie wird einfach als "Referenz als Klassenmitglied" bezeichnet .

Ich gehe davon aus, dass der möglicherweise große Aufwand beim Kopieren eines großen komplexen Objekts vermieden werden soll.

Ja und auch Szenarien, in denen Sie die Lebensdauer eines Objekts einem anderen Objekt zuordnen möchten.

Ist das im Allgemeinen eine gute Praxis? Gibt es Fallstricke bei diesem Ansatz?

Hängt von Ihrer Nutzung ab. Die Verwendung einer beliebigen Sprachfunktion entspricht der Auswahl von Pferden für Kurse . Es ist wichtig zu beachten, dass jede ( fast alle ) Sprachfunktion vorhanden ist, da sie in einigen Szenarien nützlich ist.
Bei der Verwendung von Referenzen als Klassenmitglieder sind einige wichtige Punkte zu beachten:

  • Sie müssen sicherstellen, dass das referenzierte Objekt garantiert existiert, bis Ihr Klassenobjekt existiert.
  • Sie müssen das Mitglied in der Initialisierungsliste des Konstruktormitglieds initialisieren. Sie können keine verzögerte Initialisierung durchführen , was bei einem Zeigerelement möglich sein könnte.
  • Der Compiler generiert die Kopierzuordnung nicht operator=()und Sie müssen selbst eine bereitstellen. Es ist umständlich zu bestimmen, welche Maßnahmen Ihr =Bediener in einem solchen Fall ergreifen soll. Im Grunde genommen wird Ihre Klasse nicht mehr zuweisbar .
  • Verweise können nicht gemacht werden NULL, um auf ein anderes Objekt zu verweisen. Wenn Sie erneut setzen müssen, ist dies mit einer Referenz nicht möglich, wie im Fall eines Zeigers.

Für die meisten praktischen Zwecke (es sei denn, Sie sind wirklich besorgt über eine hohe Speichernutzung aufgrund der Elementgröße) sollte es ausreichen, nur eine Elementinstanz anstelle eines Zeigers oder Referenzelements zu haben. Dies erspart Ihnen eine Menge Sorgen über andere Probleme, die Referenz- / Zeigerelemente auf Kosten der zusätzlichen Speichernutzung mit sich bringen.

Wenn Sie einen Zeiger verwenden müssen, stellen Sie sicher, dass Sie einen intelligenten Zeiger anstelle eines Rohzeigers verwenden. Das würde Ihr Leben mit Zeigern viel einfacher machen.

Alok Speichern
quelle
" Ich gehe davon aus, dass der möglicherweise große Aufwand für das Kopieren eines großen komplexen Objekts vermieden werden soll ? Ja" - erläutern Sie bitte, warum dieses Muster Ihrer Meinung nach für die Vermeidung des Kopierens relevant ist, da ich keines sehen kann. Wenn dieses Nicht-'idiom 'Kopien für irgendjemanden als Nebeneffekt seiner eigentlichen Zwecke speichert, war das ursprüngliche Design anfangs fatal fehlerhaft, und es ist unwahrscheinlich, dass es durch das Ersetzen durch dieses Muster ersetzt wird .
underscore_d
@underscore_d Angenommen, eine Klasse benötigt eine nicht triviale Menge an const-Daten und es können viele Instanzen dieser Klasse gleichzeitig vorhanden sein. Es kann unannehmbar verschwenderisch sein, wenn jede Instanz eine eigene Kopie dieser const-Daten hat. Das Speichern eines konstanten Verweises auf einen externen Speicherort dieser Daten, die gemeinsam genutzt werden können, spart also viel Kopieren. shared_ptr ist nicht unbedingt eine Lösung, da das Datenhandle nicht unbedingt dynamisch zugewiesen werden muss.
Unwichtig
@Unwichtig Ich bin mir nicht sicher, was mein Einwand im Jahr 2016 war. Ich verwende ständig Referenzen als Klassenmitglieder. Vielleicht war ich besorgt, dass das bloße Ersetzen von By-Value-Besitz durch Referenzen zu lebenslangen Fragen führen würde und nicht unbedingt ein Panacaea oder etwas ist, das immer 1: 1 getan werden kann. Ich weiß nicht.
underscore_d
1

C ++ bietet einen guten Mechanismus zum Verwalten der Lebensdauer eines Objekts durch Klassen- / Strukturkonstrukte. Dies ist eine der besten Funktionen von C ++ gegenüber anderen Sprachen.

Wenn Sie Mitgliedsvariablen durch ref oder Zeiger verfügbar machen, verletzt dies im Prinzip die Kapselung. Diese Redewendung ermöglicht es dem Verbraucher der Klasse, den Zustand eines Objekts von A zu ändern, ohne dass es (A) Kenntnis oder Kontrolle darüber hat. Es ermöglicht dem Verbraucher auch, über die Lebensdauer des Objekts von A hinaus an einem Verweis / Zeiger auf den internen Zustand von A festzuhalten. Dies ist ein schlechtes Design. Stattdessen könnte die Klasse so umgestaltet werden, dass sie einen Verweis / Zeiger auf das gemeinsam genutzte Objekt enthält (nicht dessen Eigentümer), und diese könnten mithilfe des Konstruktors festgelegt werden (Mandate the Life Time Rules). Die Klasse des gemeinsam genutzten Objekts kann so konzipiert sein, dass sie je nach Fall Multithreading / Parallelität unterstützt.

Indy9000
quelle
2
Der Code im OP enthält jedoch keinen Verweis auf eine Mitgliedsvariable. Es hat eine Mitgliedsvariable, die eine Referenz ist. Also, tolle Punkte, aber scheinbar nichts miteinander zu tun.
underscore_d
-2

Mitgliederreferenzen werden normalerweise als schlecht angesehen. Sie machen das Leben schwer im Vergleich zu Mitgliedszeigern. Aber es ist weder besonders ungewöhnlich noch eine spezielle Redewendung oder Sache. Es ist nur Aliasing.

Hündchen
quelle
23
Können Sie Hinweise auf Support geben, die normalerweise als schlecht angesehen werden ?
David Rodríguez - Dribeas