Frage Was sind die Grundregeln und Redewendungen für das Überladen von Operatoren?


Hinweis: Die Antworten wurden in gegeben eine bestimmte Reihenfolge, aber da viele Benutzer die Antworten nach den Stimmen sortieren, anstatt nach der Zeit, in der sie gegeben wurden, ist hier ein Index der Antworten in der Reihenfolge, in der sie am sinnvollsten sind:

(Hinweis: Dies ist ein Eintrag zu Die C ++ - FAQ von Stack Overflow. Wenn Sie die Idee einer FAQ in diesem Formular kritisieren möchten, dann das Posting auf Meta, mit dem alles begann wäre der richtige Ort dafür. Antworten auf diese Frage werden in der C ++ Chatroom, wo die FAQ-Idee von Anfang an begann, also wird Ihre Antwort sehr wahrscheinlich von denen gelesen, die auf die Idee gekommen sind.)  


1841
2017-12-12 12:44


Ursprung


Antworten:


Häufige Operatoren überladen

Die meisten Arbeiten, bei denen Bediener überlastet werden, sind Code für Kesselplatten. Das ist kein Wunder, da Operatoren nur syntaktischer Zucker sind, ihre eigentliche Arbeit könnte durch einfache Funktionen erledigt werden (und wird oft an diese weitergeleitet). Aber es ist wichtig, dass Sie diesen Code für die Kesselplatte erhalten. Wenn Sie scheitern, wird entweder der Code Ihres Operators nicht kompiliert oder der Code Ihres Benutzers wird nicht kompiliert, oder der Code Ihrer Benutzer wird sich überraschend verhalten.

Aufgabenverwalter

Es gibt eine Menge über die Aufgabe zu sagen. Das meiste davon wurde jedoch bereits in GMans berühmte Copy-And-Swap-FAQ, also werde ich das meiste hier überspringen und nur den perfekten Zuweisungsoperator als Referenz aufführen:

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

Bitshift-Operatoren (für Stream I / O)

Die Bitshift-Operatoren << und >>Obwohl sie immer noch in Hardware-Schnittstellen für die Bitmanipulationsfunktionen verwendet werden, die sie von C erben, sind sie in den meisten Anwendungen als überlastete Stream-Eingabe- und Ausgabeoperatoren häufiger geworden. Hinweise zum Überladen als Bitmanipulationsoperatoren finden Sie im folgenden Abschnitt Binäre Arithmetikoperatoren. Wenn Sie Ihr eigenes benutzerdefiniertes Format und eine neue Analyselogik implementieren möchten, wenn Ihr Objekt mit Iostreams verwendet wird, fahren Sie fort.

Die Stream-Operatoren, unter den am häufigsten überladenen Operatoren, sind binäre Infix-Operatoren, für die die Syntax keine Beschränkung angibt, ob sie Member oder Nicht-Member sein sollen. Da sie ihr linkes Argument ändern (sie ändern den Status des Streams), sollten sie gemäß den Faustregeln als Mitglieder des Typs ihres linken Operanden implementiert werden. Ihre linken Operanden sind jedoch Streams aus der Standardbibliothek, und obwohl die meisten Stream-Ausgabe- und Eingabeoperatoren, die von der Standardbibliothek definiert werden, tatsächlich als Member der Streamklassen definiert sind, wenn Sie Ausgabe- und Eingabeoperationen für Ihre eigenen Typen implementieren Die Stream-Typen der Standardbibliothek können nicht geändert werden. Deshalb müssen Sie diese Operatoren für Ihre eigenen Typen als Nicht-Member-Funktionen implementieren. Die kanonischen Formen der beiden sind diese:

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // write obj to stream

  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // read obj from stream

  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);

  return is;
}

Bei der Implementierung operator>>Das manuelle Festlegen des Status des Streams ist nur erforderlich, wenn das Lesen selbst erfolgreich war, aber das Ergebnis ist nicht das, was zu erwarten wäre.

Funktionsaufruf-Operator

Der Funktionsaufrufoperator, der zum Erstellen von Funktionsobjekten, auch Funktoren genannt, verwendet wird, muss als a definiert sein Mitglied Funktion, so hat es immer das implizite this Argument der Mitgliedsfunktionen. Ansonsten kann es überladen werden, um eine beliebige Anzahl von zusätzlichen Argumenten aufzunehmen, einschließlich Null.

Hier ist ein Beispiel für die Syntax:

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

Verwendung:

foo f;
int a = f("hello");

In der C ++ - Standardbibliothek werden Funktionsobjekte immer kopiert. Ihre eigenen Funktionsobjekte sollten daher kostengünstig zu kopieren sein. Wenn ein Funktionsobjekt unbedingt Daten verwenden muss, die teuer zu kopieren sind, ist es besser, diese Daten an anderer Stelle zu speichern und das Funktionsobjekt darauf verweisen zu lassen.

Vergleichsoperatoren

Die binären Infix-Vergleichsoperatoren sollten nach den Faustregeln als Nichtmitgliedsfunktionen implementiert sein1. Die unäre Präfix-Negation ! sollte (nach den gleichen Regeln) als Mitgliedfunktion implementiert werden. (aber es ist normalerweise keine gute Idee, es zu überladen.)

Die Algorithmen der Standardbibliothek (z.B. std::sort()) und Typen (z.B. std::map) wird immer nur erwarten operator< präsent sein. Aber die Benutzer Ihres Typs erwarten, dass alle anderen Operatoren vorhanden sindauch wenn du das definierst operator<, beachte, dass du die dritte Grundregel der Operatorüberladung befolgst und auch alle anderen booleschen Vergleichsoperatoren definierst. Der kanonische Weg, sie zu implementieren, ist dies:

inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}

Die wichtige Sache, die hier zu beachten ist, ist, dass nur zwei dieser Operatoren tatsächlich irgendetwas tun, die anderen nur ihre Argumente an eine dieser beiden weiterleiten, um die eigentliche Arbeit zu tun.

Die Syntax zum Überladen der verbleibenden binären booleschen Operatoren (||, &&) folgt den Regeln der Vergleichsoperatoren. Wie auch immer es ist sehr unwahrscheinlich, dass Sie einen vernünftigen Anwendungsfall für diese finden würden2.

1  Wie bei allen Faustregeln kann es auch Gründe geben, diesen zu brechen. Wenn ja, vergessen Sie nicht, dass der linke Operand der binären Vergleichsoperatoren, die für Elementfunktionen sein werden *this, muss sein const, auch. Ein Vergleichsoperator, der als Memberfunktion implementiert ist, müsste also diese Signatur haben:

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(Beachten Sie das const Am Ende.)

2  Es sollte beachtet werden, dass die integrierte Version von || und && Verwenden Sie Abkürzungssemantik. Während die benutzerdefinierten (weil sie syntaktischer Zucker für Methodenaufrufe sind) keine Abkürzungssemantik verwenden. Der Benutzer wird erwarten, dass diese Operatoren eine Abkürzungssemantik haben, und ihr Code kann davon abhängen. Daher wird es NIEMALS empfohlen, sie zu definieren.

Rechenzeichen

Unäre arithmetische Operatoren

Die unären Inkrementierungs- und Dekrementierungsoperatoren kommen sowohl in Präfix- als auch in Postfixaroma vor. Um sich voneinander zu unterscheiden, nehmen die Postfix-Varianten ein zusätzliches dummy int-Argument an. Wenn Sie Inkrementieren oder Dekrementieren überlasten, achten Sie darauf, immer die Präfix- und Postfix-Versionen zu implementieren. Hier ist die kanonische Implementierung von Inkrement, Dekrement folgt den gleichen Regeln:

class X {
  X& operator++()
  {
    // do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

Beachten Sie, dass die Postfix-Variante als Präfix implementiert ist. Beachten Sie auch, dass postfix eine zusätzliche Kopie erstellt.2

Das Überladen von Minus und Plus ist nicht sehr häufig und wird wahrscheinlich am besten vermieden. Bei Bedarf sollten sie wahrscheinlich als Elementfunktionen überladen werden.

2  Beachten Sie auch, dass die Postfix-Variante mehr Arbeit leistet und daher weniger effizient ist als die Präfix-Variante. Dies ist ein guter Grund, Präfix-Inkrement über Postfix-Inkrement generell zu bevorzugen. Während Compiler normalerweise die zusätzliche Arbeit des Postfixinkrements für eingebaute Typen weg optimieren können, können sie möglicherweise nicht dasselbe für benutzerdefinierte Typen tun (was etwas so unschuldig aussehen könnte wie ein Listeniterator). Sobald du dich daran gewöhnt hast i++Es wird sehr schwer, sich daran zu erinnern ++i statt wann i ist kein eingebauter Typ (außerdem müssten Sie den Code ändern, wenn Sie einen Typ ändern), daher ist es besser, die Präfix-Inkremente immer zu verwenden, es sei denn, Postfix wird explizit benötigt.

Binäre arithmetische Operatoren

Vergessen Sie für die binären arithmetischen Operatoren nicht, den dritten Grundregeloperator zu überladen: Wenn Sie angeben +, stelle ausserdem zur Verfügung +=, wenn Sie zur Verfügung stellen -nicht weglassen -=Andrew Koenig war der erste, der beobachtete, dass die zusammengesetzten Zuweisungsoperatoren als Basis für ihre nicht zusammengesetzten Gegenstücke verwendet werden können. Das heißt, Operator + ist in Bezug auf implementiert +=, - ist in Bezug auf implementiert -= etc.

Gemäß unseren Faustregeln, + und seine Begleiter sollten Nicht-Mitglieder sein, während ihre zusammengesetzten Aufgaben (+= etc.), sollte ihr linkes Argument ändern, sollte ein Mitglied sein. Hier ist der beispielhafte Code für += und +, die anderen binären arithmetischen Operatoren sollten auf die gleiche Weise implementiert werden:

class X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};
inline X operator+(X lhs, const X& rhs)
{
  lhs += rhs;
  return lhs;
}

operator+= gibt sein Ergebnis pro Referenz zurück, während operator+ Gibt eine Kopie des Ergebnisses zurück. Natürlich ist die Rückgabe einer Referenz normalerweise effizienter als die Rückgabe einer Kopie, aber im Fall von operator+Es gibt keinen Weg um das Kopieren. Wenn du schreibst a + b, erwarten Sie, dass das Ergebnis ein neuer Wert ist, weshalb operator+ muss einen neuen Wert zurückgeben.3 Beachten Sie auch das operator+ Nimmt seinen linken Operanden durch Kopie anstatt durch const Referenz. Der Grund dafür ist der gleiche wie der Grund dafür operator= nimmt sein Argument pro Kopie.

Die Bitmanipulationsoperatoren ~  &  |  ^  <<  >> sollte wie die arithmetischen Operatoren implementiert werden. Allerdings (außer Überladung << und >> Für die Ausgabe und Eingabe gibt es nur sehr wenige sinnvolle Anwendungsfälle, um diese zu überlasten.

3  Auch hier ist die Lektion, die daraus zu ziehen ist a += b ist im Allgemeinen effizienter als a + b und sollte wenn möglich bevorzugt werden.

Array-Subskription

Der Array-Subscript-Operator ist ein binärer Operator, der als Klassenmitglied implementiert werden muss. Es wird für containerartige Typen verwendet, die den Zugriff auf ihre Datenelemente über einen Schlüssel ermöglichen. Die kanonische Form der Bereitstellung dieser ist dies:

class X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

Es sei denn, Sie möchten nicht, dass Benutzer Ihrer Klasse die von zurückgegebenen Datenelemente ändern können operator[] (In diesem Fall können Sie die nicht-konstante Variante weglassen), sollten Sie immer beide Varianten des Operators angeben.

Wenn bekannt ist, dass value_type auf einen integrierten Typ verweist, sollte die const-Variante des Operators eine Kopie anstelle einer const-Referenz zurückgeben.

Operatoren für Zeigerartige Typen

Um eigene Iteratoren oder Smart Pointer zu definieren, müssen Sie den Dereferenzierungsoperator für unäre Präfixe überladen * und der Zugriffsoperator für den binären Infixzeiger ->:

class my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

Beachten Sie, dass diese auch fast immer eine const und eine nicht-const Version benötigen. Für die -> Betreiber, wenn value_type ist von class (oder struct oder union) Typ, ein anderer operator->() wird rekursiv aufgerufen, bis ein operator->() Gibt einen Wert vom Nicht-Klassentyp zurück.

Der unäre Adressenoperator sollte niemals überladen werden.

Zum operator->*() sehen diese Frage. Es wird selten benutzt und daher selten überladen. Tatsächlich überlasten selbst Iteratoren sie nicht.


Weiter Konvertierungsoperatoren


896
2017-12-12 12:47



Die drei Grundregeln des Überladens von Operatoren in C ++

Wenn es zum Überladen von Operatoren in C ++ kommt, gibt es drei grundlegende Regeln, denen Sie folgen sollten. Wie bei allen solchen Regeln gibt es tatsächlich Ausnahmen. Manchmal sind Menschen von ihnen abgewichen und das Ergebnis war kein schlechter Code, aber solche positiven Abweichungen sind selten. Zumindest waren 99 von 100 solcher Abweichungen, die ich gesehen habe, ungerechtfertigt. Es könnte aber genauso gut 999 von 1000 gewesen sein. Sie sollten sich also besser an die folgenden Regeln halten.

  1. Wenn die Bedeutung eines Operators nicht offensichtlich klar und unbestritten ist, sollte er nicht überladen werden.  Stellen Sie stattdessen eine Funktion mit einem gut gewählten Namen bereit.
    Grundsätzlich lautet die oberste Regel für die Überlastung von Betreibern: Tu es nicht. Das mag seltsam erscheinen, denn es gibt eine Menge über die Überlastung von Operatoren zu wissen, und deshalb beschäftigen sich viele Artikel, Buchkapitel und andere Texte mit all dem. Aber trotz dieser scheinbar offensichtlichen Beweise, Es gibt nur überraschend wenige Fälle, in denen eine Überlastung des Bedieners angebracht ist. Der Grund ist, dass es eigentlich schwierig ist, die Semantik hinter der Anwendung eines Operators zu verstehen, es sei denn, die Verwendung des Operators in der Anwendungsdomäne ist bekannt und unbestritten. Entgegen der landläufigen Meinung ist dies kaum der Fall.

  2. Bleiben Sie immer bei der bekannten Semantik des Betreibers.
    C ++ hat keine Einschränkungen für die Semantik von überladenen Operatoren. Ihr Compiler akzeptiert gerne Code, der die Binärdatei implementiert + Operator von seinem rechten Operanden subtrahieren. Die Benutzer eines solchen Operators würden jedoch niemals den Ausdruck vermuten a + b zu subtrahieren a von b. Dies setzt natürlich voraus, dass die Semantik des Operators in der Anwendungsdomäne unumstritten ist.

  3. Stellen Sie immer alle aus einer Reihe verwandter Operationen bereit.
    Operatoren sind miteinander verwandtund zu anderen Operationen. Wenn Ihr Typ dies unterstützt a + bBenutzer erwarten, dass sie anrufen können a += b, auch. Wenn es Präfixinkrement unterstützt ++awerden sie erwarten a++ auch zu arbeiten. Wenn sie überprüfen können, ob a < bSie werden mit Sicherheit erwarten, auch prüfen zu können, ob a > b. Wenn sie Ihren Typ kopieren können, erwarten sie auch, dass die Zuweisung funktioniert.


Weiter Die Entscheidung zwischen Mitglied und Nichtmitglied.


440
2017-12-12 12:45



Die allgemeine Syntax des Überladens von Operatoren in C ++

Sie können die Bedeutung von Operatoren für integrierte Typen in C ++ nicht ändern, Operatoren können nur für benutzerdefinierte Typen überladen werden1. Das heißt, mindestens einer der Operanden muss vom benutzerdefinierten Typ sein. Wie bei anderen überladenen Funktionen können Operatoren für einen bestimmten Parametersatz nur einmal überladen werden.

Nicht alle Operatoren können in C ++ überladen werden. Unter den Operatoren, die nicht überlastet werden können, sind: .  ::  sizeof  typeid  .* und der einzige ternäre Operator in C ++, ?: 

Zu den Operatoren, die in C ++ überladen werden können, gehören:

  • Rechenzeichen: +  -  *  /  % und +=  -=  *=  /=  %= (alle binären Infix); +  - (unäres Präfix); ++  -- (unäres Präfix und Postfix)
  • Bit-Manipulation: &  |  ^  <<  >> und &=  |=  ^=  <<=  >>= (alle binären Infix); ~ (unäres Präfix)
  • boolsche Algebra: ==  !=  <  >  <=  >=  ||  && (alle binären Infix); ! (unäres Präfix)
  • Speicherverwaltung: new  new[]  delete  delete[]
  • implizite Konvertierungsoperatoren
  • Verschiedenes: =  []  ->  ->*  ,  (alle binären Infix); *  & (alles unäre Präfix) () (Funktionsaufruf, n-ary Infix)

Aber die Tatsache, dass Sie kann Überlastung all dieser Dinge bedeutet nicht Sie sollte tun Sie dies. Beachten Sie die Grundregeln für das Überladen von Operatoren.

In C ++ sind Operatoren in Form von überladen Funktionen mit speziellen Namen. Wie bei anderen Funktionen können überladene Operatoren im Allgemeinen entweder als Mitgliedsfunktion des Typs ihres linken Operanden oder wie Nichtmitgliedsfunktionen. Ob Sie frei entscheiden können oder eines der beiden verwenden möchten, hängt von mehreren Kriterien ab.2 Ein unärer Operator @3, angewendet auf ein Objekt x, wird entweder als aufgerufen operator@(x) oder wie x.operator@(). Ein binärer Infix-Operator @, auf die Objekte angewendet x und y, heißt entweder als operator@(x,y) oder wie x.operator@(y).4 

Operatoren, die als Nicht-Mitgliedsfunktionen implementiert sind, sind manchmal Freunde ihres Operandentyps.

1  Der Begriff "benutzerdefiniert" könnte leicht irreführend sein. C ++ unterscheidet zwischen eingebauten Typen und benutzerdefinierten Typen. Zu den ersten gehören zum Beispiel int, char und double; Zu Letzteren gehören alle Struktur-, Klassen-, Vereinigungs- und Aufzählungstypen, einschließlich der aus der Standardbibliothek, auch wenn sie nicht als solche von Benutzern definiert sind.

2  Dies ist abgedeckt in ein späterer Teil dieser FAQ.

3  Das @ ist kein gültiger Operator in C ++, weshalb ich ihn als Platzhalter verwende.

4  Der einzige ternäre Operator in C ++ kann nicht überladen werden, und der einzige n-stufige Operator muss immer als Elementfunktion implementiert sein.


Weiter Die drei Grundregeln des Überladens von Operatoren in C ++.


229
2017-12-12 12:46



Die Entscheidung zwischen Mitglied und Nichtmitglied

Die binären Operatoren = (Zuordnung), [] (Array-Abonnement), -> (Mitgliederzugriff), sowie das n-ary ()(Funktionsaufruf) -Operator, muss immer als implementiert werden Mitgliedsfunktionen, weil die Syntax der Sprache es erfordert.

Andere Operatoren können entweder als Mitglieder oder als Nicht-Mitglieder implementiert werden. Einige davon müssen jedoch in der Regel als Nicht-Member-Funktionen implementiert werden, da ihr linker Operand nicht von Ihnen geändert werden kann. Die bekanntesten sind die Eingabe- und Ausgabeoperatoren << und >>, deren linke Operanden Stream-Klassen aus der Standardbibliothek sind, die Sie nicht ändern können.

Für alle Operatoren, bei denen Sie wählen müssen, ob Sie sie als Member-Funktion oder als Nicht-Member-Funktion implementieren möchten, Verwenden Sie die folgenden Faustregeln zu entscheiden:

  1. Wenn es ein ist unärer Operator, implementiere es als Mitglied Funktion.
  2. Wenn ein binärer Operator behandelt beide Operanden gleich (es lässt sie unverändert), implementieren Sie diesen Operator als Nicht-Mitglied Funktion.
  3. Wenn ein binärer Operator dies tut nicht Behandle beide Operanden gleichermaßen (normalerweise ändert es seinen linken Operanden), es könnte nützlich sein, es zu a zu machen Mitglied Funktion des Typs des linken Operanden, wenn er auf die privaten Teile des Operanden zugreifen muss.

Wie bei allen Faustregeln gibt es natürlich Ausnahmen. Wenn Sie einen Typ haben

enum Month {Jan, Feb, ..., Nov, Dec}

und Sie möchten die Inkrement- und Dekrementoperatoren dafür überladen, Sie können dies nicht als Elementfunktionen ausführen, da Enumerationstypen in C ++ keine Elementfunktionen haben können. Sie müssen es also als freie Funktion überladen. Und operator<() Eine Klassenvorlage, die in einer Klassenvorlage verschachtelt ist, ist viel einfacher zu schreiben und zu lesen, wenn sie in der Klassendefinition inline ausgeführt wird. Aber das sind in der Tat seltene Ausnahmen.

(Jedoch, ob Sie machen eine Ausnahme, vergessen Sie nicht das Problem von const-ness für den Operanden, der für Elementfunktionen zum Impliziten wird this Streit. Wenn der Operator als Nichtmitgliedsfunktion sein am weitesten links liegendes Argument als a const Referenz, der gleiche Operator wie eine Member-Funktion muss eine haben const am Ende zu machen *this ein const Referenz.)


Weiter Häufige Operatoren überladen.


211
2017-12-12 12:49



Konvertierungsoperatoren (auch als benutzerdefinierte Conversions bezeichnet)

In C ++ können Sie Konvertierungsoperatoren erstellen, Operatoren, mit denen der Compiler zwischen Ihren Typen und anderen definierten Typen konvertieren kann. Es gibt zwei Arten von Konvertierungsoperatoren, implizite und explizite.

Implizite Konvertierungsoperatoren (C ++ 98 / C ++ 03 und C ++ 11)

Ein impliziter Konvertierungsoperator ermöglicht es dem Compiler implizit zu konvertieren (wie die Konvertierung zwischen int und long) der Wert eines benutzerdefinierten Typs für einen anderen Typ.

Das Folgende ist eine einfache Klasse mit einem impliziten Konvertierungsoperator:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

Implizite Konvertierungsoperatoren, wie Konstruktoren mit einem Argument, sind benutzerdefinierte Konvertierungen. Compiler gewähren eine benutzerdefinierte Konvertierung, wenn versucht wird, einen Aufruf einer überladenen Funktion abzugleichen.

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

Dies scheint zunächst sehr hilfreich zu sein, aber das Problem dabei ist, dass die implizite Konvertierung sogar einsetzt, wenn es nicht erwartet wird. Im folgenden Code, void f(const char*)wird da heißen my_string() ist nicht ein Wert, also stimmt die erste nicht überein:

void f(my_string&);
void f(const char*);

f(my_string());

Anfänger bekommen das leicht falsch und selbst erfahrene C ++ Programmierer sind manchmal überrascht, weil der Compiler eine Überladung auswählt, die sie nicht vermutet haben. Diese Probleme können durch explizite Konvertierungsoperatoren gemildert werden.

Explizite Konvertierungsoperatoren (C ++ 11)

Anders als implizite Konvertierungsoperatoren treten explizite Konvertierungsoperatoren niemals ein, wenn Sie dies nicht erwarten. Das Folgende ist eine einfache Klasse mit einem expliziten Konvertierungsoperator:

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

Beachten Sie die explicit. Wenn Sie nun versuchen, den unerwarteten Code von den impliziten Konvertierungsoperatoren auszuführen, erhalten Sie einen Compilerfehler:

prog.cpp: In der Funktion 'int main ()':
prog.cpp: 15: 18: Fehler: keine passende Funktion für den Aufruf von 'f (my_string)'
prog.cpp: 15: 18: Hinweis: Kandidaten sind:
prog.cpp: 11: 10: note: void f (mein_String &)
prog.cpp: 11: 10: Hinweis: keine bekannte Konvertierung für Argument 1 von "my_string" zu "my_string &"
prog.cpp: 12: 10: hinweis: void f (const char *)
prog.cpp: 12: 10: Hinweis: keine bekannte Konvertierung für Argument 1 von 'my_string' zu 'const char *'

Um den expliziten Cast-Operator aufzurufen, müssen Sie ihn verwenden static_cast, eine C-Style-Besetzung oder eine Konstrukteurstil-Besetzung (d. h. T(value) ).

Es gibt jedoch eine Ausnahme: Der Compiler darf implizit in konvertieren bool. Darüber hinaus darf der Compiler nach dem Konvertieren keine weitere implizite Konvertierung durchführen bool (Ein Compiler darf 2 implizite Konvertierungen gleichzeitig ausführen, aber maximal 1 benutzerdefinierte Konvertierung).

Weil der Compiler nicht "vorbei" wirft bool, explizite Konvertierungsoperatoren entfernen jetzt die Notwendigkeit für die Sicheres Bool-Idiom. Zum Beispiel haben intelligente Zeiger vor C ++ 11 das Safe Bool-Idiom verwendet, um Konvertierungen in ganzzahlige Typen zu verhindern. In C ++ 11 verwenden die Smartpointer stattdessen einen expliziten Operator, da der Compiler nicht implizit in einen Integraltyp konvertiert werden darf, nachdem er einen Typ explizit in bool konvertiert hat.

Weiter Überlastung new und delete.


143
2018-05-17 18:32



Überlastung new und delete

Hinweis: Dies betrifft nur die Syntax Überlastung new und deletenicht mit dem Implementierung von solchen überlasteten Operatoren. Ich denke, dass die Semantik der Überladung new und delete verdienen ihre eigenen FAQIm Bereich des Überladens von Operatoren kann ich dem niemals gerecht werden.

Grundlagen

In C ++, wenn Sie eine schreiben neuer Ausdruck mögen new T(arg) Wenn dieser Ausdruck ausgewertet wird, passieren zwei Dinge: Erstens operator new wird aufgerufen, um rohen Speicher zu erhalten, und dann den entsprechenden Konstruktor von T wird aufgerufen, um diesen rohen Speicher in ein gültiges Objekt umzuwandeln. Wenn Sie ein Objekt löschen, wird zuerst der Destruktor aufgerufen und anschließend der Speicher zurückgegeben operator delete.
Mit C ++ können Sie diese beiden Operationen optimieren: Speicherverwaltung und die Konstruktion / Zerstörung des Objekts im zugewiesenen Speicher. Letzteres geschieht durch Schreiben von Konstruktoren und Destruktoren für eine Klasse. Die Feinabstimmung der Speicherverwaltung erfolgt, indem Sie Ihre eigenen schreiben operator new und operator delete.

Die erste der Grundregeln des Überladens von Operatoren - mach es nicht - gilt besonders für Überladung new und delete. Fast die einzigen Gründe, diese Betreiber zu überlasten, sind Leistungsprobleme und Speicherbeschränkungenund in vielen Fällen andere Aktionen, wie Änderungen an den Algorithmen verwendet, wird viel bieten höheres Kosten / Gewinn-Verhältnis als zu versuchen, die Speicherverwaltung zu optimieren.

Die C ++ - Standardbibliothek enthält eine Reihe von vordefinierten new und delete Betreiber. Die wichtigsten sind diese:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

Die ersten beiden ordnen Speicher für ein Objekt zu, und zwar für ein Array von Objekten. Wenn Sie Ihre eigenen Versionen von diesen zur Verfügung stellen, werden sie nicht überladen, sondern ersetzen die aus der Standardbibliothek.
Wenn Sie überladen operator new, sollten Sie immer auch das Matching überladen operator delete, selbst wenn du es nie nennen willst. Der Grund dafür ist, dass, wenn ein Konstruktor während der Auswertung eines neuen Ausdrucks einen Fehler auslöst, das Laufzeitsystem den Speicher an den Benutzer zurückgibt operator delete passend zum operator new Das wurde aufgerufen, um den Speicher zuzuordnen, in dem das Objekt erstellt wird. Wenn Sie keine Übereinstimmung angeben operator deletewird der Standardname genannt, der fast immer falsch ist.
Wenn Sie überladen new und deletesollten Sie auch die Array-Varianten überladen.

Platzierung new

C ++ ermöglicht neuen und löschenden Operatoren, zusätzliche Argumente zu übernehmen.
Das so genannte Placement New ermöglicht das Erstellen eines Objekts an einer bestimmten Adresse, die an Folgendes übergeben wird:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

Die Standardbibliothek enthält die entsprechenden Überladungen der neuen Operatoren delete und delete:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

Beachten Sie, dass im obigen Beispielcode für die Platzierung neu operator delete wird nie aufgerufen, außer der Konstruktor von X löst eine Ausnahme aus.

Sie können auch überladen new und delete mit anderen Argumenten. Wie beim zusätzlichen Argument für die Platzierung neu, werden diese Argumente in Klammern hinter dem Schlüsselwort aufgeführt new. Lediglich aus historischen Gründen werden solche Varianten oft auch als Placement New bezeichnet, auch wenn ihre Argumente nicht darin bestehen, ein Objekt an einer bestimmten Adresse zu platzieren.

Klassenspezifisch neu und löschen

In den meisten Fällen sollten Sie die Speicherverwaltung optimieren, da Messungen gezeigt haben, dass Instanzen einer bestimmten Klasse oder einer Gruppe zugehöriger Klassen häufig erstellt und zerstört werden und dass die Standardspeicherverwaltung des Laufzeitsystems optimiert ist allgemeine Leistung, befasst sich in diesem speziellen Fall ineffizient. Um dies zu verbessern, können Sie neu laden und für eine bestimmte Klasse löschen:

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

So überladen, verhalten sich new und delete wie statische Member-Funktionen. Für Objekte von my_class, das std::size_t Argument wird immer sein sizeof(my_class). Diese Operatoren werden jedoch auch für dynamisch zugeordnete Objekte von aufgerufen abgeleitete KlassenIn diesem Fall könnte es größer sein.

Global neu und löschen

Überschreiben Sie die vordefinierten Operatoren der Standardbibliothek durch unsere eigenen, um das globale new und delete zu überladen. Dies muss jedoch selten getan werden.


130
2017-12-12 13:07



Warum kann nicht operator<< Funktion zum Streaming von Objekten an std::cout oder zu einer Datei eine Mitgliedsfunktion sein?

Sagen wir, du hast:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

In Anbetracht dessen können Sie nicht verwenden:

Foo f = {10, 20.0};
std::cout << f;

Schon seit operator<< ist als Memberfunktion von überladen Foo, die LHS des Betreibers muss ein sein Foo Objekt. Das bedeutet, dass Sie Folgendes benötigen:

Foo f = {10, 20.0};
f << std::cout

das ist sehr nicht intuitiv.

Wenn Sie es als Nichtmitgliedsfunktion definieren,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

Sie können Folgendes verwenden:

Foo f = {10, 20.0};
std::cout << f;

Das ist sehr intuitiv.


29
2018-01-22 19:00