Wilkening-Online Logo

C++ Operator-Überladung von A bis (fast) Z



Von Detlef Wilkening
16.05.2012
Version: 1

Hinweis - der Artikel ist hier noch nicht vollständig wiedergegeben (Kapitel 3 & 4 fehlen noch). Ein Ausschnitt aus dem vollständigen Artikel entspricht den Folien des Vortrags, den ich am 16.05.2012 auf dem 6-ten C++ User-Treffen in Düsseldorf gehalten habe.

1. Einführung

1.1 Operator-Überladung

1.2 Die C++ Operatoren

Dieses Kapitel ist eine Übersicht über alle C++ Operatoren. Die Tabelle ist dabei nach der Priorität der Operatoren geordnet. Außerdem enthält sie noch die Informationen über die Auswertungs-Reihenfolge (L=>R oder R=>L), und ob sich der Operator überladen läßt (+) oder nicht (-) - und wenn, ob nur als Element-Funktion (EF).

Prio. Operator Beschreibung Ausw. Überl.
1 :: Bereichszuordnung L=>R -
. Komponenten-Zugriff L=>R -
2 .-> Element-Zugriff, bzw. Punkt-Pfeil-Operator L=>R +
-> Element-Zugriff, bzw. Pfeil-Operator L=>R EF
[ ] Index (Array-Zugriff) L=>R EF
( ) Funktions-Aufruf L=>R EF
++ Post-Increment (n++) L=>R +
-- Post-Increment (n--) L=>R +
typeid Typ (RTTI) L=>R -
const_cast Const-Cast L=>R -
static_cast Static-Cast L=>R -
reinterpret_cast Reinterpret-Cast L=>R -
dynamisc_cast Dynamic-Cast L=>R -
3 sizeof Primäre Objekt-Größe R=>L -
++ Prä-Increment (++n) R=>L +
-- Prä-Decrement (--n) R=>L +
~ 1-er Komplement R=>L +
! Logisches Not R=>L +
+ Unäres + (Vorzeichen) R=>L +
- Unäres - (Vorzeichen) R=>L +
& Unäres & (Adresse) R=>L +
* Unäres * (Dereferenzierung) R=>L +
new, delete, new[], ... Dynamische Speicherverwaltung R=>L +
( ) Klassischer C-Cast R=>L -
4 .* Zeiger auf Element L=>R -
->* Zeiger auf Element L=>R +
5 * Multiplikation (binäres *) L=>R +
/ Division L=>R +
% Modulo L=>R +
6 + Addition (binäres +) L=>R +
- Subtraktion (binäres -) L=>R +
7 << Ausgabe bzw. Bit-Links-Schiebe L=>R +
>> Eingabe bzw. Bit-Rechts-Schiebe L=>R +
8 < Kleiner L=>R +
> Größer L=>R +
<= Kleiner-Gleich L=>R +
>= Größer-Gleich L=>R +
9 == Gleich L=>R +
!= Ungleich L=>R +
10 & Bitweises Und (binäres &) L=>R +
11 ^ Bitweises XOR L=>R +
12 | Bitweises Oder L=>R +
13 && Logisches Und L=>R +
14 || Logisches Oder L=>R +
15 ? : Bedingung R=>L -
16 = Zuweisung R=>L EF
*= Multiplikations-Zuweisung R=>L EF
/= Divisions-Zuweisung R=>L EF
%= Modulo-Zuweisung R=>L EF
+= Additions-Zuweisung R=>L EF
-= Subtraktions-Zuweisung R=>L EF
<<= Links-Bit-Schiebe-Zuweisung R=>L EF
>>= Rechts-Bit-Schiebe-Zuweisung R=>L EF
&= Bitweise-Und-Zuweisung R=>L EF
|= Bitweise-Oder-Zuweisung R=>L EF
^= Bitweise-Xor-Zuweisung R=>L EF
17 , Komma L=>R +

Zusätzlich lassen sich noch Konvertierungs-Operatoren als Element-Funktinonen überladen. Diese stehen dann für implizite benutzerdefinierte Typ-Umwandlungen - z.B. bei Funktions-Aufrufen - zur Verfügung (siehe Kapitel 3.9). Seit C++11 können diese auch "explizit" gemacht werden.

2. Grundlagen

2.1 Operatoren als Element-Funktionen


#include <iostream>
using namespace std;

class Rational
{
public:
   Rational(int n=0, int d=1) : nume(n), deno(d) {}

   Rational mul(const Rational&) const;

   void print() const { cout << nume << '/' << deno << endl; }

private:
   int nume, deno;
};

Rational Rational::mul(const Rational& arg) const
{
   return Rational(nume*arg.nume, deno*arg.deno);
}

int main()
{
   Rational r0, r1(2, 3), r2(5, 7);
   r0.print();
   r1.print();
   r2.print();

   r0 = r1.mul(r2);
   r0.print();
}


Ausgabe:

0/1
2/3
5/7
10/21


#include <iostream>
using namespace std;

class Rational
{
public:
   Rational(int n=0, int d=1) : nume(n), deno(d) {}

   Rational mul(const Rational&) const;

   Rational operator*(const Rational&) const;

   void print() const { cout << nume << '/' << deno << endl; }

private:
   int nume, deno;
};

Rational Rational::mul(const Rational& arg) const
{
   return Rational(nume*arg.nume, deno*arg.deno);
}

Rational Rational::operator*(const Rational& arg) const
{
   return Rational(nume*arg.nume, deno*arg.deno);
}

int main()
{
   Rational r0, r1(2, 3), r2(5, 7);
   r0.print();
   r1.print();
   r2.print();

   r0 = r1.mul(r2);
   r0.print();

   r0 = r1.operator*(r2);           // Funktions-Schreibweise
   r0.print();

   r0 = r1*r2;                      // Operator-Schreibweise
   r0.print();
}


Ausgabe:

0/1
2/3
5/7
10/21
10/21
10/21

2.2 Operatoren als freie Funktionen


#include <iostream>
using namespace std;

class Rational
{
public:
   Rational(int n=0, int d=1) : nume(n), deno(d) {}

   friend Rational mul(const Rational&, const Rational&);

   void print() const { cout << nume << '/' << deno << endl; }

private:
   int nume, deno;
};

Rational mul(const Rational& larg, const Rational& rarg)
{
   return Rational(larg.nume*rarg.nume, larg.deno*rarg.deno);
}

int main()
{
   Rational r0, r1(2, 3), r2(5, 7);
   r0.print();
   r1.print();
   r2.print();

   r0 = mul(r1, r2);
   r0.print();
}


Ausgabe:

0/1
2/3
5/7
10/21


#include <iostream>
using namespace std;

class Rational
{
public:
   Rational(int n=0, int d=1) : nume(n), deno(d) {}

   friend Rational mul(const Rational&, const Rational&);

   friend Rational operator*(const Rational&, const Rational&);

   void print() const { cout << nume << '/' << deno << endl; }

private:
   int nume, deno;
};

Rational mul(const Rational& larg, const Rational& rarg)
{
   return Rational(larg.nume*rarg.nume, larg.deno*rarg.deno);
}

Rational operator*(const Rational& larg, const Rational& rarg)
{
   return Rational(larg.nume*rarg.nume, larg.deno*rarg.deno);
}

int main()
{
   Rational r0, r1(2, 3), r2(5, 7);
   r0.print();
   r1.print();
   r2.print();

   r0 = mul(r1, r2);
   r0.print();

   r0 = operator*(r1, r2);          // Funktions-Schreibweise
   r0.print();

   r0 = r1*r2;                      // Operator-Schreibweise
   r0.print();
}


Ausgabe:

0/1
2/3
5/7
10/21
10/21
10/21

2.3 Der Vergleich zwischen beiden Varianten

2.3.1 Symmetrische Operator-Nutzung


#include <iostream>
using namespace std;

class Rational
{
public:
   Rational(int n=0, int d=1) : nume(n), deno(d) {}

   Rational operator*(const Rational&) const;

   void print() const { cout << nume << '/' << deno << endl; }

private:
   int nume, deno;
};

Rational Rational::operator*(const Rational& arg) const
{
   return Rational(nume*arg.nume, deno*arg.deno);
}

int main()
{
   Rational r0, r1(2, 3), r2(5, 7);
   r0.print();
   r1.print();
   r2.print();

   r0 = r1 * 4;                     // Okay => r0 = r1 * Rational(4);
   r0.print();

   r0 = 4 * r2;                     // Compiler-Fehler - geht nicht bei Element-Funktionen
   r0.print();
}


#include <iostream>
using namespace std;

class Rational
{
public:
   Rational(int n=0, int d=1) : nume(n), deno(d) {}

   friend Rational operator*(const Rational&, const Rational&);

   void print() const { cout << nume << '/' << deno << endl; }

private:
   int nume, deno;
};

Rational operator*(const Rational& larg, const Rational& rarg)
{
   return Rational(larg.nume*rarg.nume, larg.deno*rarg.deno);
}

int main()
{
   Rational r0, r1(2, 3), r2(5, 7);
   r0.print();
   r1.print();
   r2.print();

   r0 = r1 * 4;                     // Okay => r0 = r1 * Rational(4);
   r0.print();

   r0 = 4 * r2;                     // Okay => r0 = Rational(4) * r2;
   r0.print();
}


Ausgabe:

0/1
2/3
5/7
8/3
20/7

2.3.2 Operatoren für fremde Klassen


// So geht es leider nicht

namespace std
{
   template<...> class basic_ostream
   {
   public:
      ...
      ostream& operator<<(const Rational&);      // Sehr sehr unwahrscheinlich (siehe Text)
      ...
   };
}


#include <iostream>
using namespace std;

class Rational
{
public:
   Rational(int n=0, int d=1) : nume(n), deno(d) {}

   friend Rational operator*(const Rational&, const Rational&);

   friend ostream& operator<<(ostream&, const Rational&);

private:
   int nume, deno;
};

Rational operator*(const Rational& larg, const Rational& rarg)
{
   return Rational(larg.nume*rarg.nume, larg.deno*rarg.deno);
}

ostream& operator<<(ostream& out, const Rational& arg)
{
   return out << arg.nume << '/' << arg.deno;
}

int main()
{
   Rational r0, r1(2, 3), r2(5, 7);
   cout << r0 << endl;
   cout << r1 << endl;
   cout << r2 << endl;

   r0 = r1 * r2;
   cout << r0 << endl;

   r0 = r1 * 4;
   cout << r0 << endl;

   r0 = 4 * r2;
   cout << r0 << endl;
}


Ausgabe:

0/1
2/3
5/7
10/21
8/3
20/7

2.3.3 Operatoren für Enums


#include <iostream>
using namespace std;

enum E { one, two, three };

inline ostream& operator<<(ostream& out, E e)
{
   static const char* text[] = { "eins", "zwei", "drei" };
   return out << text[e];
}

inline E& operator++(E& e)
{
   static E next[] = { two, three, one };
   return e=next[e];
}

int main()
{
   E e = one;
   cout << e << endl;           // => eins

   ++e;
   cout << e << endl;           // => zwei
   ++e;
   cout << e << endl;           // => drei
   ++e;
   cout << e << endl;           // => eins

   ++++e;
   cout << e << endl;           // => drei
}


Ausgabe:

eins
zwei
drei
eins
drei

2.3.4 Zusammenfassung

2.3.5 Zwei weitere Beispiele für freie Operator-Funktionen


#include <iostream>
#include <string>
#include <boost/lexical_cast.hpp>
using namespace std;

string operator+(string s, int n)
{
   s += boost::lexical_cast<string>(n);
   return s;
}

int main()
{
   string s("abc->");
   cout << s << endl;            // abc->
   s = s + 2;
   cout << s << endl;            // abc->2
   s = s + 34;
   cout << s << endl;            // abc->234
}


Ausgabe:

abc->
abc->2
abc->234


#include <iostream>
#include <string>
#include <boost/lexical_cast.hpp>
using namespace std;

string& operator<<(string& s, bool b)
{
   s += b ? "true" : "false";
   return s;
}

template<class T> string& operator<<(string& s, const T& t)
{
   s += boost::lexical_cast<string>(t);
   return s;
}

int main()
{
   string s;

   s << true << " ist: " << 1 << " + " << 2 << ' ' << "= " << 3;
   cout << s << endl;

   (s="") << 3.5 << ' ' << false;
   cout << s << endl;
}


Ausgabe:

true ist: 1 + 2 = 3
3.5 false

2.4 Sinn der funktionalen Schreibweise beim Aufruf


#include <iostream>
#include <string>
using namespace std;

class A
{
public:
   virtual string operator+(const A&) const;
};

string A::operator+(const A&) const
{
   return "A";
}

class B : public A
{
public:
   virtual string operator+(const A&) const override;
};

string B::operator+(const A& a) const
{
   string res = A::operator+(a);         // Aufruf der Basis-Klassen-Funktion geht nur so
   res += "B";
   return res;
}

int main()
{
   A a1, a2;
   string res = a1 + a2;
   cout << res << endl;

   B b;
   res = b + a2;
   cout << res << endl;
}


Ausgabe:

A
AB

3. Operator-Besonderheiten

todo

3.1 Zuweisungs-Operatoren

todo

3.2 Unäre Operatoren

todo

3.3 Prä- und Post-Increment und Decrement Operatoren

todo

3.4 Logisches "Und" und "Oder"

todo

3.5 Adress-Operator

todo

3.6 Element-Zugriff bzw. Pfeil-Operator

todo

3.7 Index-Operator bzw. Array-Zugriff

todo

3.8 Funktions-Aufruf-Operatoren

todo

3.9 Konvertierungs-Operatoren

todo

4. Boost.Operator

todo

4.1 Einführung

todo

4.2 Operator-Konzepte

todo

4.3 Composite-Konzepte

todo

4.4 Fazit

todo

5. Fazit

5.1 Was fehlte?

5.2 Wichtig für C++

5.3 Fazit

6. Literatur

C++ Bücher, die sich nur oder zumindest zu einem großen Teil mit Operator-Überladung beschäftigen, kenne ich nicht. Natürlich enthält wohl jedes Lehrbuch zu C++ auch ein Kapitel zur Operator-Überladung - unterm Strich wird sie aber im Verhältnis zu anderen Themen doch immer recht kurz behandelt - daher ja auch dieser Artikel.

Trotzdem haben es 2 Bücher in die Literatur-Liste zur C++ Operator-Überladung geschafft.

7. Links

Ähnlich wie bei der Literatur, kenne ich keine Web-Seiten, die sich dem Thema "Operator-Überladung" widmen - man gut, daß ich nun diesen Artikel geschrieben habe. Trotzdem gibt es hier einige Links zu ein paar speziellen Themen aus diesem Artikel:
  1. Die Boost Web-Seite im Internet:

  2. Ein Einführungs-Artikel in Boost von mir:

  3. Mein Artikel zum Thema "override in C++11"

8. Versions-Historie

Die Versions-Historie dieses Artikels:
Schlagwörter: