Benutzerdefinierte Typkonvertierungen (C++)
Eine Konvertierung erzeugt einen neuen Wert eines Typs aus einem Wert eines anderen Typs. Standardkonvertierungen sind in die C++-Sprache integriert und unterstützen die integrierten Typen, und Sie können benutzerdefinierte Konvertierungen erstellen , um Konvertierungen in, von oder zwischen benutzerdefinierten Typen durchzuführen.
Die Standardkonvertierungen konvertieren zwischen integrierten Typen, zwischen Zeigern oder Verweisen auf Typen, die durch Vererbung verwandt sind, von und zu void-Zeigern und zum NULL-Zeiger. Weitere Informationen finden Sie unter Standardkonvertierungen. Benutzerdefinierte Konvertierungen konvertieren zwischen benutzerdefinierten Typen oder zwischen benutzerdefinierten Typen und integrierten Typen. Sie können sie als Konvertierungskonstruktoren oder als Konvertierungsfunktionen implementieren.
Konvertierungen können entweder explizit sein—wenn ein Programmierer einen Typen z. B. durch eine Umwandlung oder direkte Initialisierung in einen anderen Typen konvertiert—oder implizit—wenn die Sprache oder das Programm einen anderen Typen als den vom Programmierer angegeben verlangt.
Implizite Konvertierungen werden in den folgenden Fällen ausgeführt:
Wenn ein Argument einer Funktion nicht vom gleichen Typ ist wie der entsprechende Parameter.
Wenn ein Rückgabewert einer Funktion nicht vom gleichen Typ ist wie der Rückgabetyp der Funktion.
Wenn ein Initialisiererausdruck nicht vom gleichen Typ ist wie das initialisierte Objekt.
Wenn ein Ausdruck, der eine Bedingungsanweisung, ein Schleifenkonstrukt oder einen Switch kontrolliert, nicht den richtigen Ergebnistyp für diese Kontrolle hat.
Wenn ein an einen Operator übergebener Operand nicht vom gleichen Typ ist wie der entsprechende Operand-Parameter. Für integrierte Operatoren müssen beide Operanden vom gleichen Typ sein und werden in einen gemeinsamen Typen konvertiert, der beide Operanden darstellen kann. Weitere Informationen finden Sie unter Standardkonvertierungen. Für benutzerdefinierte Operatoren müssen alle Operanden vom gleichen Typ sein wie der entsprechende Operand-Parameter.
Wenn eine Standardkonvertierung eine implizite Konvertierung nicht ausführen kann, kann der Compiler eine benutzerdefinierte Konvertierung verwenden, optional gefolgt von einer zusätzlichen Standardkonvertierung zur Vervollständigung.
Wenn zwei oder mehrere benutzerdefinierte Konvertierungen mit derselben Konvertierung an einem Konvertierungsort verfügbar sind, wird die Konvertierung auch als "Mehrdeutig" bezeichnet. Solche Mehrdeutigkeiten führen zu einem Fehler, da der Compiler nicht ermitteln kann, welche der verfügbaren Konvertierungen ausgeführt werden soll. Die Definition mehrerer Wege für dieselbe Konvertierung ist jedoch nicht zwangsläufig ein Fehler, da sich die Konvertierungen an unterschiedlichen Stellen im Quellcode befinden können, z. B. in Abhängigkeit von den Headerdateien der jeweiligen Quelldatei. Sofern an jedem Konvertierungsort nur eine Konvertierung verfügbar ist, liegt keine Mehrdeutigkeit vor. Mehrdeutige Konvertierungen können auf verschiedene Arten entstehen. Die gängigsten Arten sind:
Mehrfachvererbung. Die Konvertierung ist in mehr als einer Basisklasse definiert.
Mehrdeutige Funktionsaufrufe. Die Konvertierung ist gleichzeitig als Konvertierungskonstruktor des Zieltyps und als Konvertierungsfunktion des Quelltyps definiert. Weitere Informationen finden Sie unter Konvertierungsfunktionen.
Mehrdeutigkeiten können normalerweise aufgelöst werden, in dem der Name des betreffenden Typs vollständiger angegeben wird, oder durch eine explizite Umwandlung zur Klarstellung Ihrer Absicht.
Sowohl Konvertierungskonstruktoren als auch Konvertierungsfunktionen folgen den Regeln für die Memberzugriffssteuerung. Die Zugänglichkeit der Konvertierungen wird jedoch nur berücksichtigt, wenn eine eindeutige Konvertierung ermittelt werden kann. Eine Konvertierung kann also auch dann mehrdeutig sein, wenn die Zugriffsebene einer konkurrierenden Konvertierung deren Ausführung verhindern würde. Weitere Informationen zur Barrierefreiheit von Membern finden Sie unter "Elementzugriffssteuerung".
Das Schlüsselwort explicit und Probleme mit impliziter Konvertierung
Wenn Sie eine benutzerdefinierte Konvertierung erstellen, kann der Compiler diese standardmäßig für implizite Konvertierungen verwenden. Manchmal ist dies erwünscht, aber in anderen Fällen können die einfachen Regeln des Compilers für implizite Konvertierungen dazu führen, dass dieser unerwünschten Code akzeptiert.
Ein bekanntes Beispiel für eine implizite Konvertierung, die Probleme verursachen kann, ist die Konvertierung in bool
. Es gibt viele Gründe, aus denen Sie einen Klassentyp erstellen möchten, der in einem booleschen Kontext verwendet werden kann, z. B. damit sie zum Steuern einer if
Anweisung oder Schleife verwendet werden kann, aber wenn der Compiler eine benutzerdefinierte Konvertierung in einen integrierten Typ durchführt, darf der Compiler anschließend eine zusätzliche Standardkonvertierung anwenden. Die Absicht dieser zusätzlichen Standardkonvertierung besteht darin, Aktionen wie die Heraufstufung short
von zu zu int
ermöglichen, aber es öffnet auch die Tür für weniger offensichtliche Konvertierungen , z. B. von bool
zu int
, wodurch Ihr Klassentyp in ganzzahligen Kontexten verwendet werden kann, die Sie nie beabsichtigt haben. Dieses spezielle Problem wird als Tresor Bool-Problem bezeichnet. Diese Art von Problem ist der Ort, an dem die explicit
Schlüsselwort (keyword) helfen kann.
Der explicit
Schlüsselwort (keyword) teilt dem Compiler mit, dass die angegebene Konvertierung nicht zum Ausführen impliziter Konvertierungen verwendet werden kann. Wenn Sie die syntaktische Bequemlichkeit impliziter Konvertierungen vor der Einführung der explicit
Schlüsselwort (keyword) wollten, mussten Sie entweder die unbeabsichtigten Folgen akzeptieren, die implizite Konvertierung manchmal erstellt oder weniger bequem, benannte Konvertierungsfunktionen als Problemumgehung verwendet. Mithilfe der explicit
Schlüsselwort (keyword) können Sie nun praktische Konvertierungen erstellen, die nur zum Ausführen expliziter Umwandlungen oder direkter Initialisierung verwendet werden können, und dies führt nicht zu der Art von Problemen, die durch das Tresor Bool-Problem veranschaulicht werden.
Die explicit
Schlüsselwort (keyword) kann seit C++98 auf Konvertierungskonstruktoren und auf Konvertierungsfunktionen seit C++11 angewendet werden. Die folgenden Abschnitte enthalten weitere Informationen zur Verwendung des explicit
Schlüsselwort (keyword).
Konvertierungskonstruktoren
Konvertierungskonstruktoren definieren Konvertierungen von benutzerdefinierten oder integrierten Typen zu einem benutzerdefinierten Typ. Im folgenden Beispiel wird ein Konvertierungskonstruktor veranschaulicht, der vom integrierten Typ double
in einen benutzerdefinierten Typ Money
konvertiert wird.
#include <iostream>
class Money
{
public:
Money() : amount{ 0.0 } {};
Money(double _amount) : amount{ _amount } {};
double amount;
};
void display_balance(const Money balance)
{
std::cout << "The balance is: " << balance.amount << std::endl;
}
int main(int argc, char* argv[])
{
Money payable{ 79.99 };
display_balance(payable);
display_balance(49.95);
display_balance(9.99f);
return 0;
}
Beachten Sie, dass der erste Aufruf der Funktion display_balance
, die ein Argument vom Typ Money
entgegennimmt, keine Konvertierung erfordert, da das Argument bereits vom korrekten Typ ist. Im zweiten Aufruf von display_balance
" ist jedoch eine Konvertierung erforderlich, da der Typ des Arguments, ein double
Wert mit 49.95
einem Wert, nicht das ist, was die Funktion erwartet. Die Funktion kann diesen Wert nicht direkt verwenden, aber da es eine Konvertierung vom Typ des Arguments gibt –double
in den Typ des übereinstimmenden Parameters –Money
wird ein temporärer Typwert Money
aus dem Argument erstellt und zum Abschließen des Funktionsaufrufs verwendet. Beachten Sie im dritten Aufruf display_balance
, dass es sich bei dem Argument nicht um ein double
, sondern um einen float
Wert von 9.99
– handelt, und dennoch kann der Funktionsaufruf noch abgeschlossen werden, da der Compiler eine Standardkonvertierung ausführen kann ( in diesem Fall von float
zu double
- und führt dann die benutzerdefinierte Konvertierung von double
bis zum Money
Abschluss der erforderlichen Konvertierung aus.
Deklarieren von Konvertierungskonstruktoren
Beim Deklarieren von Konvertierungskonstruktoren gelten die folgenden Regeln:
Der Zieltyp der Konvertierung ist der benutzerdefinierte Typ, der konstruiert wird.
Konvertierungskonstruktoren erwarten normalerweise genau ein Argument, das vom Quelltyp ist. Konvertierungskonstruktoren können jedoch zusätzliche Parameter definieren, wenn jeder dieser zusätzlichen Parameter einen Standardwert hat. Der Quelltyp ist weiterhin der Typ des ersten Parameters.
Konvertierungskonstruktoren definieren wie auch alle anderen Konstruktoren keinen Rückgabetyp. Die Angabe eines Rückgabetyps bei der Deklaration führt zu einem Fehler.
Konvertierungskonstruktoren können explizit sein.
Explizite Konvertierungskonstruktoren
Durch das Deklarieren eines Konvertierungskonstruktors explicit
kann er nur verwendet werden, um eine direkte Initialisierung eines Objekts durchzuführen oder eine explizite Umwandlung durchzuführen. Damit wird verhindert, dass Funktionen, die ein Argument vom Klassentyp erwarten, ebenfalls implizit Argument vom Quelltyp des Konvertierungskonstruktors akzeptieren. Außerdem wird verhindert, dass der Klassentyp aus einem Wert des Quelltyps durch Kopieren initialisiert wird. Das folgende Beispiel zeigt die Definition eines expliziten Konvertierungskonstruktors und dessen Auswirkungen auf die Wohlgeformtheit des Codes.
#include <iostream>
class Money
{
public:
Money() : amount{ 0.0 } {};
explicit Money(double _amount) : amount{ _amount } {};
double amount;
};
void display_balance(const Money balance)
{
std::cout << "The balance is: " << balance.amount << std::endl;
}
int main(int argc, char* argv[])
{
Money payable{ 79.99 }; // Legal: direct initialization is explicit.
display_balance(payable); // Legal: no conversion required
display_balance(49.95); // Error: no suitable conversion exists to convert from double to Money.
display_balance((Money)9.99f); // Legal: explicit cast to Money
return 0;
}
In diesem Beispiel können Sie den expliziten Konvertierungskonstruktor weiterhin für die direkte Initialisierung von payable
verwenden. Falls Sie jedoch Money payable = 79.99;
durch Kopieren initialisieren, erhalten Sie einen Fehler. Der erste Aufruf von display_balance
ist nicht betroffen, da das Argument vom korrekten Typ ist. Der zweite Aufruf von display_balance
führt zu einem Fehler, da der Konvertierungskonstruktor nicht für implizite Konvertierungen verwendet werden kann. Der dritte Aufruf ist display_balance
aufgrund der expliziten Umwandlung Money
legal, aber beachten Sie, dass der Compiler weiterhin geholfen hat, die Umwandlung abzuschließen, indem eine implizite Umwandlung von float
in .double
Obwohl die Vorzüge impliziter Konvertierungen verlockend sind, können diese zu schwer auffindbaren Bugs führen. Als Standardregel sollten alle Konvertierungskonstruktoren explizit sein, es sei denn, Sie möchten eine bestimmte Konvertierung implizit erlauben.
Konvertierungsfunktionen
Konvertierungsfunktionen definieren Konvertierungen von einem benutzerdefinierten Typ zu anderen Typen. Diese Funktionen werden manchmal auch als "Umwandlungsoperatoren" bezeichnet, da sie zusammen mit Konvertierungskonstruktoren aufgerufen werden, wenn ein Wert zu einem anderen Typ umgewandelt wird. Das folgende Beispiel veranschaulicht eine Konvertierungsfunktion, die vom benutzerdefinierten Typ in einen integrierten Typ Money
konvertiert wird: double
#include <iostream>
class Money
{
public:
Money() : amount{ 0.0 } {};
Money(double _amount) : amount{ _amount } {};
operator double() const { return amount; }
private:
double amount;
};
void display_balance(const Money balance)
{
std::cout << "The balance is: " << balance << std::endl;
}
Beachten Sie, dass die Membervariable amount
privat gemacht wird und dass eine öffentliche Konvertierungsfunktion in Typ double
eingeführt wird, um den Wert von amount
. In der Funktion display_balance
erfolgt eine implizite Konvertierung, wenn der Wert von balance
durch den Operator zum Einfügen des Datenstroms <<
in die Standardausgabe geschrieben wird. Da kein Datenstromeinfügeoperator für den benutzerdefinierten Typ definiert ist, es aber einen für den integrierten Typ Money
double
gibt, kann der Compiler die Konvertierungsfunktion Money
verwenden, um double
den Stream-Einfügeoperator zu erfüllen.
Konvertierungsfunktionen werden an abgeleitete Klassen vererbt. Konvertierungsfunktionen in abgeleiteten Klassen überschreiben geerbte Konvertierungsfunktionen nur dann, wenn diese zum exakt gleichen Typ konvertieren. Eine benutzerdefinierte Konvertierungsfunktion des abgeleiteten Klassenoperators int überschreibt beispielsweise keine benutzerdefinierte Konvertierungsfunktion des Basisklassenoperators kurz, obwohl die Standardkonvertierungen eine Konvertierungsbeziehung zwischen int
und .short
Deklarieren von Konvertierungsfunktionen
Beim Deklarieren von Konvertierungsfunktionen gelten die folgenden Regeln:
Der Zieltyp der Konvertierung muss vor der Deklaration der Konvertierungsfunktion deklariert werden. Klassen, Strukturen, Enumerationen und typedefs können nicht innerhalb der Deklaration der Konvertierungsfunktion deklariert werden.
operator struct String { char string_storage; }() // illegal
Konvertierungsfunktionen akzeptieren keine Argumente. Die Angabe von Parametern bei der Deklaration führt zu einem Fehler.
Der Rückgabetyp einer Konvertierungsfunktion wird durch deren Namen festgelegt, der gleichzeitig der Name des Zieltyps der Konvertierung ist. Die Angabe eines Rückgabetyps bei der Deklaration führt zu einem Fehler.
Konvertierungsfunktionen können virtual sein.
Konvertierungsfunktionen können explizit sein.
Explizite Konvertierungsfunktionen
Wenn eine Konvertierungsfunktion als explizit deklariert wird, kann diese nur für explizite Umwandlungen verwendet werden. Damit wird verhindert, dass Funktionen, die ein Argument vom Zieltyp der Konvertierungsfunktion erwarten, ebenfalls implizit Argument vom Klassentyp akzeptieren. Außerdem wird verhindert, dass Instanzen des Zieltyps aus einem Wert des Klassentyps durch Kopieren initialisiert werden. Das folgende Beispiel zeigt die Definition einer expliziten Konvertierungsfunktion und deren Auswirkungen auf die Wohlgeformtheit des Codes.
#include <iostream>
class Money
{
public:
Money() : amount{ 0.0 } {};
Money(double _amount) : amount{ _amount } {};
explicit operator double() const { return amount; }
private:
double amount;
};
void display_balance(const Money balance)
{
std::cout << "The balance is: " << (double)balance << std::endl;
}
Hier wurde der Konvertierungsfunktionsoperator "Double" explizit erstellt, und in der Funktion display_balance
wurde eine explizite Umwandlung in den Typ double
eingeführt, um die Konvertierung auszuführen. Ohne diese Umwandlung wäre der Compiler nicht in der Lage, den geeigneten Operator zum Einfügen des Datenstroms <<
für den Typ Money
zu ermitteln, und ein Fehler wäre die Folge.
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für