c++ - overload - Was sind die Grundregeln und Redewendungen für das Überladen von Operatoren?




c++ overload operator<< (5)

Hinweis: Die Antworten wurden in einer bestimmten Reihenfolge gegeben , aber da viele Benutzer die Antworten nach Abstimmungen sortieren, anstatt nach der Zeit, in der sie gegeben wurden, folgt hier ein Index der Antworten in der Reihenfolge, in der sie am sinnvollsten sind:

(Hinweis: Dies ist ein Eintrag in die C ++ - FAQ von Stack Overflow . Wenn Sie die Idee, eine FAQ in diesem Formular bereitzustellen, kritisieren möchten, dann wäre das Posting auf meta, mit dem all dies begonnen wurde , der richtige Ort dafür Diese Frage wird im C ++ - Chatraum überwacht, wo die FAQ-Idee von Anfang an begann, so dass Ihre Antwort sehr wahrscheinlich von denjenigen gelesen wird, die die Idee hatten.)


Überladen von new und delete

Anmerkung: Dies betrifft nur die Syntax des Überladens von new und delete , und nicht die Implementierung solcher überladener Operatoren. Ich denke, dass die Semantik des Überladens von new und delete ihre eigenen FAQ verdient , im Bereich der Überlastung von Betreibern kann ich es niemals gerecht werden.

Grundlagen

Wenn Sie in C ++ einen neuen Ausdruck wie new T(arg) schreiben, passieren zwei Dinge, wenn dieser Ausdruck ausgewertet wird: Der erste operator new wird aufgerufen, um rohen Speicher zu erhalten, und dann wird der entsprechende Konstruktor von T aufgerufen, um diesen rohen Speicher in a umzuwandeln gültiges Objekt Wenn Sie ein Objekt löschen, wird zuerst sein Destruktor aufgerufen und anschließend wird der Speicher an den 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 Ihren eigenen operator new und den operator delete schreiben.

Die erste der grundlegenden Regeln des Überladens von Operatoren - tun Sie es nicht - gilt insbesondere für das Überladen new und delete . Fast die einzigen Gründe, diese Operatoren zu überlasten, sind Leistungsprobleme und Speicherbeschränkungen , und in vielen Fällen ergeben andere Aktionen, wie Änderungen an den verwendeten Algorithmen , ein viel höheres Kosten / Gewinn-Verhältnis als der Versuch, die Speicherverwaltung zu optimieren.

Die C ++ - Standardbibliothek enthält eine Reihe vordefinierter Operatoren für new und delete . 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 bereitstellen, werden sie nicht überladen, sondern die aus der Standardbibliothek ersetzen .
Wenn Sie den operator new überladen, sollten Sie auch immer den entsprechenden operator delete , auch wenn Sie ihn nie aufrufen möchten. Der Grund dafür ist, dass, wenn ein Konstruktor während der Auswertung eines neuen Ausdrucks auslöst, das Laufzeitsystem den Speicher an den operator delete , der dem operator new , der zum Zuweisen des Speichers zum Erstellen des Objekts aufgerufen wurde. Wenn Sie dies tun keinen passenden operator delete , der standardmäßig aufgerufen wird, der fast immer falsch ist.
Wenn Sie new und delete überladen, sollten Sie auch die Array-Varianten überlasten.

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 new der operator delete nie aufgerufen wird, es sei denn, der Konstruktor von X löst eine Ausnahme aus.

Sie können auch neu laden und mit anderen Argumenten delete . Wie beim zusätzlichen Argument für die Platzierung neu, werden diese Argumente in Klammern nach dem Schlüsselwort 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

Most commonly you will want to fine-tune memory management because measurement has shown that instances of a specific class, or of a group of related classes, are created and destroyed often and that the default memory management of the run-time system, tuned for general performance, deals inefficiently in this specific case. To improve this, you can overload new and delete for a specific class:

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);
    // ... 
}; 

Overloaded thus, new and delete behave like static member functions. For objects of my_class , the std::size_t argument will always be sizeof(my_class) . However, these operators are also called for dynamically allocated objects of derived classes , in which case it might be greater than that.

Global new and delete

To overload the global new and delete, simply replace the pre-defined operators of the standard library with our own. However, this rarely ever needs to be done.


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 werden 1 . 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:. :: typeid .* und der einzige ternäre Operator in C ++, ?:

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

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

Die Tatsache, dass Sie alle überladen können, bedeutet jedoch nicht, dass Sie dies tun sollten . Beachten Sie die Grundregeln für das Überladen von Operatoren.

In C ++ werden Operatoren in Form von Funktionen mit speziellen Namen überladen. Wie bei anderen Funktionen können überladene Operatoren im Allgemeinen entweder als eine Elementfunktion ihres linken Operandentyps oder als Nichtmitgliedsfunktionen implementiert werden. 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 , der auf ein Objekt x angewendet wird, wird entweder als [email protected](x) oder als [email protected]() . Ein binärer Infixoperator @ , angewendet auf die Objekte x und y , wird entweder als [email protected](x,y) oder als [email protected](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 wird in einem späteren Teil dieser FAQ behandelt.

3 Das @ ist in C ++ kein gültiger Operator, weshalb ich es 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.

Fahren Sie mit den drei Grundregeln des Überladens von Operatoren in C ++ fort .


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ühmten Copy-And-Swap FAQ gesagt, daher werde ich das meiste hier weglassen 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 Hardwareschnittstellen für die Bitmanipulationsfunktionen verwendet werden, die sie von C erben, sind in den meisten Anwendungen als überlastete Stream-Eingabe- und Ausgabeoperatoren vorherrschender 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 Einschrä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;
}

Beim Implementieren von operator>> ist das manuelle Festlegen des Status des Streams 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 verwendet wird, die auch als Funktoren bezeichnet werden, muss als Elementfunktion definiert sein, sodass er immer das implizite Argument der Elementfunktionen enthält. 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 gemäß den Faustregeln als Nichtmitgliedsfunktionen implementiert werden 1 . Das 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 ) erwarten immer nur, dass der operator< vorhanden ist. Die Benutzer Ihres Typs erwarten jedoch, dass auch alle anderen Operatoren vorhanden sind. Wenn Sie also den operator< definieren, müssen Sie die dritte Grundregel der Operatorüberladung befolgen und auch alle anderen booleschen Vergleichsoperatoren definieren. 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 für das Überladen der verbleibenden binären booleschen Operatoren ( || , && ) folgt den Regeln der Vergleichsoperatoren. Es ist jedoch sehr unwahrscheinlich, dass Sie einen vernünftigen Anwendungsfall für diese 2 finden würden .

1 Wie bei allen Faustregeln gibt es manchmal auch Gründe, diese zu brechen. Vergessen Sie in diesem Fall nicht, dass der linke Operand der binären Vergleichsoperatoren, der für Elementfunktionen *this ist, auch const muss. 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 die const am Ende.)

2 Es sollte beachtet werden, dass die integrierte Version von || und && verwenden 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 Inkrement oder Dekrement überladen, stellen Sie sicher, dass Sie sowohl die Präfix- als auch die Postfix-Versionen 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 Sie sich daran gewöhnt haben, i++ zu tun, wird es sehr schwer, sich daran zu erinnern, ++i zu tun, wenn i nicht von einem integrierten Typ bin (außerdem müssten Sie Code ändern, wenn Sie einen Typ ändern), also ist es besser Machen Sie es sich zur Gewohnheit, Präfix-Inkremente immer zu verwenden, es sei denn, Postfix wird explizit benötigt.

Binäre arithmetische Operatoren

Vergessen Sie bei den binären arithmetischen Operatoren nicht, den dritten Grundregeloperator zu überladen: Wenn Sie + , geben Sie auch += , wenn Sie - , nicht weglassen -= usw. Andrew Koenig soll der Erste gewesen sein zu beobachten, dass die zusammengesetzten Zuweisungsoperatoren als eine Basis für ihre nicht zusammengesetzten Gegenstücke verwendet werden können. Das heißt, Operator + wird in Form von += implementiert, - wird in Form von -= usw. implementiert.

Gemäß unseren Faustregeln sollten + und seine Begleiter keine Mitglieder sein, während ihre Gegenstücke aus der zusammengesetzten Aufgabe ( += usw.), die ihr linkes Argument ändern, ein Mitglied sein sollten. 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 Verweis zurück, während operator+ eine Kopie seines Ergebnisses zurückgibt. Natürlich ist das Zurückgeben eines Verweises normalerweise effizienter als das Zurückgeben einer Kopie, aber im Fall von operator+ gibt es keinen Weg um das Kopieren herum. Wenn Sie a + b schreiben, erwarten Sie, dass das Ergebnis ein neuer Wert ist. Deshalb muss operator+ einen neuen Wert zurückgeben. 3 Beachten Sie außerdem, dass operator+ seinen linken Operanden nach Kopie und nicht nach Konstante verwendet. Der Grund dafür ist der gleiche wie der Grund, warum operator= sein Argument pro Kopie nimmt.

Die Bitmanipulationsoperatoren ~ & | ^ << >> sollte auf die gleiche Weise wie die arithmetischen Operatoren implementiert werden. Es gibt jedoch (abgesehen von der Überlastung von << und >> für die Ausgabe und Eingabe) nur sehr wenige sinnvolle Anwendungsfälle, um diese zu überlasten.

3 Auch hieraus ergibt sich, dass a += b im Allgemeinen effizienter ist als a + b und wenn möglich bevorzugt werden sollte.

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;
  // ...
};

Sofern Sie nicht möchten, dass Benutzer Ihrer Klasse Datenelemente ändern können, die von 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 Smartpointer zu definieren, müssen Sie den unären Präfix-Dereferenzoperator * und den binären Infix-Pointer-Member-Zugriffsoperator -> überladen:

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 den Operator -> , wenn value_type vom Typ class (oder struct oder union ) ist, wird ein anderer operator->() rekursiv aufgerufen, bis ein operator->() einen Wert vom Nicht-Klassen-Typ zurückgibt.

Der unäre Adressenoperator sollte niemals überladen werden.

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

Weiter zu Conversion-Operatoren


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, den Wert eines benutzerdefinierten Typs (wie die Konvertierung zwischen int und long ) implizit in einen anderen Typ zu konvertieren.

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 wird void f(const char*) aufgerufen, weil my_string() kein lvalue , also stimmt der 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 das explicit . Wenn Sie nun versuchen, den unerwarteten Code von den impliziten Konvertierungsoperatoren auszuführen, erhalten Sie einen Compilerfehler:

prog.cpp: In function ‘int main()’:
prog.cpp:15:18: error: no matching function for call to ‘f(my_string)’
prog.cpp:15:18: note: candidates are:
prog.cpp:11:10: note: void f(my_string&)
prog.cpp:11:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘my_string&’
prog.cpp:12:10: note: void f(const char*)
prog.cpp:12:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘const char*’

Um den expliziten Cast-Operator aufzurufen, müssen Sie static_cast , einen C-Style-Cast oder einen Konstruktorstil-Cast (dh T(value) ) verwenden.

Es gibt jedoch eine Ausnahme: Der Compiler darf implizit in bool konvertieren. Außerdem darf der Compiler keine weitere implizite Konvertierung durchführen, nachdem er in bool konvertiert wurde (ein Compiler darf 2 implizite Konvertierungen gleichzeitig bool , aber maximal 1 benutzerdefinierte Konvertierung).

Da der Compiler keine "Vergangenheit" bool , entfernen explizite Konvertierungsoperatoren jetzt die Notwendigkeit für das Safe 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 zu Überladen new und delete .


Why can't operator<< function for streaming objects to std::cout or to a file be a member function?

Let's say you have:

struct Foo
{
   int a;
   double b;

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

Given that, you cannot use:

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

Since operator<< is overloaded as a member function of Foo , the LHS of the operator must be a Foo object. Which means, you will be required to use:

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

which is very non-intuitive.

If you define it as a non-member function,

struct Foo
{
   int a;
   double b;
};

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

You will be able to use:

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

which is very intuitive.





c++-faq