C++ Core Guidelines: Mehr Regeln fürs Überladen

Mit dem letzten Artikel startete die Reise zum Überladen von Funktionen und Operatoren. Nun wird sie fortgesetzt und zugleich abgeschlossen.

In Pocket speichern vorlesen Druckansicht 3 Kommentare lesen
Lesezeit: 7 Min.
Von
  • Rainer Grimm

Im letzten Artikel begann ich unsere Reise zum Überladen von Funktionen und Operatoren. Heute setzte ich diese Reise sowohl fort und schließe sie zugleich ab.

Um den Faden des letzten Artikels wieder aufzunehmen. Hier sind die zehn Regeln.

Die Reise geht mit der sehr wichtigen Regel 164 weiter.

C.164: Avoid conversion operators

Falls du viel Spaß haben willst, dann überlade den Operator bool und setzte ihn nicht auf explicit. Das heißt, dass implizite Typkonveritierungen von bool to int implizit stattfinden können.

Doch ich sollte seriöser argumentieren. Das folgenden Codebeispiel enthält die Klasse MyHouse. Objekte vom Typ MyHouse können von einer Familie gekauft werden kann, sofern es leer ist. Daher ist es natürlich sehr praktisch den Operator bool zu überladen, um einfach zu testen, ob ein Haus bereits von einer Familie gekauft wurde.

// implicitConversion.cpp

#include <iostream>
#include <string>


struct MyHouse{
MyHouse() = default;
MyHouse(const std::string& fam): family(fam){}

operator bool(){ return not family.empty(); } // (1)
// explicit operator bool(){ return not family.empty(); } // (2)

std::string family = "";
};

int main(){

std::cout << std::boolalpha << std::endl;

MyHouse firstHouse;
if (not firstHouse){ // (3)
std::cout << "firstHouse is already sold." << std::endl;
};

MyHouse secondHouse("grimm"); // (4)
if (secondHouse){
std::cout << "Grimm bought secondHouse." << std::endl;
}

std::cout << std::endl;

int myNewHouse = firstHouse + secondHouse; // (5)
auto myNewHouse2 = (20 * firstHouse - 10 * secondHouse) / secondHouse;

std::cout << "myNewHouse: " << myNewHouse << std::endl;
std::cout << "myNewHouse2: " << myNewHouse2 << std::endl;

std::cout << std::endl;
}

Schnell lässt sich mit dem Operator bool prüfen (1), ob eine Familie (4) oder keine Familie (3) in dem Haus wohnt. Dank des impliziten Operators bool lassen sich nun aber auch Häuser in arithmetischen Ausdrücken (5) verwenden. Das war aber nicht in meiner Absicht.

MyHouse bietet ein viel zu mächtiges Interface an. Seit C++11 lässt sich ein Konvertierungsoperator als explizit deklarieren. Damit wendet der Compiler keine implizite Konvertierung nach int an. Kommt hingegen der explizite Operator bool zum Einsatz (2), ist die Addition von Häusern nicht mehr zulässig. Jedoch lassen sich Häuser in logischen Ausdrücken verwenden.

Nun schlägt die Übersetzung des Programms fehl.

C.165: Use using for customization points

Diese Regel ist ziemlich speziell. Daher werde ich es kurz machen. Es gibt rund 50 überladene Version von std::swap im C++-Standard. Darüber hinaus kommt es häufig vor, dass bereits eigene swap-Methoden für eigene Datentypen verwendet werden: (siehe C++ Core Guidelines: Vergleiche und die Funktionen swap und hash).

namespace N {
My_type X { /* ... */ };
void swap(X&, X&); // optimized swap for N::X
// ...
}

Dank Argument-Dependent Lookup (siehe C.168) findet der Compiler die Implementierung der Funktion swap in Namensraum N. Es ist aber eine gute Idee, die generische Funktion std::swap als eine Art Fallback zu verwenden. std::swap wird vermutlich nicht für deinen Datentyp optimiert sein, lässt sich aber zu mindestens verwenden. Die using-Anweisung im folgenden Beispiel verfolgt genau dieses Ziel.

void f3(N::X& a, N::X& b)
{
using std::swap; // make std::swap available
swap(a, b); // calls N::swap if it exists, otherwise std::swap
}

C.166: Overload unary & only as part of a system of smart pointers and references

Um ehrlich zu sein: Die Regel C.166 ist viel zu speziell, um sie in diesem Artikel genauer vorzustellen. Falls du den unären Operator & überlädtst, um einen Stellvertreter zu erzeugen, solltest du die Konsequenzen kennen.

C.167: Use an operator for an operation with its conventional meaning

Die Regel C.167 ist der Regel C.160 sehr ähnlich: C.160: Define operators primarily to mimic conventional usage. Ich schrieb bereits über C.160 in meinem letzten Artikel: C++ Core Guidelines: Überladen von Funktionen und Operatoren.

Die Regel lässt sich auf viele Operatoren anwenden.

  • <<, >>: Ein- und Ausgabe
  • ==, !=, <, <=, >, and >=: Vergleiche
  • +, -, *, /, and %: Arithmetic
  • ., ->, unary *, and []: Zugriff
  • =: Zuweisung

C.168: Define overloaded operators in the namespace of their operands

ADL ist eine besondere Art, Namen aufzulösen, die das Leben eines Entwicklers deutlich einfacher werden lässt. ADL steht für Argument-Dependet Lookup, wird aber auch gelegentlich Koenig Lookup genannt. Die besondere Art, Namen aufzulösen, besteht darin, dass für einen unqualifizierten Funktionsaufruf Funktionen im Namensraum der Funktionsargumente auch berücksichtigt werden. Mehr Details zu ADL gibt es hier: argument-dependent lookup.

Hier kommt eine kleine Erinnerungsstütze. Dank ADL findet die C++-Laufzeit in folgendem Beispiel den richtigen Operator == im Namensraum der Funktionsargumente..

namespace N {
struct S { };
bool operator==(S, S); // OK: in the same namespace as S, and even next to S
}

N::S s;

bool x = (s == s); // finds N::operator==() by ADL

C.170: If you feel like overloading a lambda, use a generic lambda

Diese Regel ist sehr leicht zu verdauen. Du kannst einen Lambda-Ausdruck nicht überladen. Mit C++14 gilt diese Einschränkung nicht mehr, denn C++14 kennt generische Lambda-Ausdrücke.

auto g = [](int) { /* ... */ };
auto g = [](double) { /* ... */ }; // error: cannot overload lambdas

auto h = [](auto) { /* ... */ }; // OK

Ein Lambda-Ausdruck ist eine Instanz einer Klasse, für die der Aufrufoperator überladen wurde. Gerne werden diese Instanzen auch Funktionsobjekte genannt. Darüber hinaus ist eine generische Lambda-Funktion ein Funktionsobjekt mit einem templifizierten Aufrufoperator. Das war bereits die ganze Theorie.

Für den speziellen Klassentyp union gibt es vier Regeln. Mir ist noch nicht klar, ob ich den ganzen nächsten Artikel benötige, um diese Regeln vorzustellen. Danach habe ich auf jeden Fall alle Regeln für Klassen und Klassenhierarchien vorgestellt und werde über Aufzählungen (enum) schreiben.

  • Ich bin froh, dass ich es geschafft habe, diese Artikel noch rechtzeitig zu schreiben. Auf dem Meeting C++ in Berlin hatte ich dieses Jahr wieder ein volles Programm aus kurzen und langen Vorträgen und einer Fragestunde. Dazu kamen natürlich auch die vielen Diskussionen zur Zukunft von C++.
  • Für meine drei offenen Seminare im ersten Halbjahr 2018 sind noch Plätze frei. Ich freue mich immer darauf, meine Leidenschaft vermitteln zu können.

()