[C++] Was bedeutet das explizite Keyword?


Answers

Angenommen, Sie haben eine Klasse String :

class String {
public:
    String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

Nun, wenn Sie versuchen:

String mystring = 'x';

Das Zeichen 'x' wird implizit in int konvertiert und dann wird der Konstruktor String(int) aufgerufen. Aber das ist nicht, was der Benutzer beabsichtigt haben könnte. Um solche Bedingungen zu vermeiden, definieren wir den Konstruktor als explicit :

class String {
public:
    explicit String (int n); //allocate n bytes
    String(const char *p); // initialize sobject with string p
};
Question

Was bedeutet das explicit Schlüsselwort in C ++?




Diese Antwort behandelt die Objekterstellung mit / ohne explizitem Konstruktor, da sie in den anderen Antworten nicht behandelt wird.

Betrachten Sie die folgende Klasse ohne expliziten Konstruktor:

class Foo
{
public:
    Foo(int x) : m_x(x)
    {
    }

private:
    int m_x;
};

Objekte der Klasse Foo können auf 2 Arten erstellt werden:

Foo bar1(10);

Foo bar2 = 20;

Abhängig von der Implementierung kann die zweite Art der Instanziierung der Klasse Foo verwirrend sein oder nicht, was der Programmierer beabsichtigt hat. Wenn das explicit Schlüsselwort dem Konstruktor Foo bar2 = 20; würde, würde ein Compilerfehler bei Foo bar2 = 20; .

In der Regel empfiehlt es sich, Konstruktoren mit einem Argument als explicit zu deklarieren, es sei denn, Ihre Implementierung verbietet dies explicit .

Beachten Sie auch, dass Konstruktoren mit

  • Standardargumente für alle Parameter, oder
  • Standardargumente für den zweiten Parameter ab

Beide können als Konstruktoren mit einem Argument verwendet werden. Vielleicht möchten Sie diese auch explicit .

Ein Beispiel, in dem Sie Ihren Konstruktor mit einem Argument nicht explizit verwenden möchten, ist, wenn Sie einen Funktor erstellen (sehen Sie sich die in this Antwort deklarierte Struktur 'add_x' an). In diesem Fall erstellen Sie ein Objekt als add_x add30 = 30; würde wahrscheinlich Sinn machen.

Here ist eine gute Übersicht über explizite Konstruktoren.




Dies wurde bereits diskutiert ( was ist expliziter Konstruktor ). Aber ich muss sagen, dass die detaillierten Beschreibungen hier fehlen.

Außerdem ist es immer eine gute Programmierpraxis, Ihre ein Argument Konstruktoren (einschließlich denen mit Standardwerten für arg2, arg3, ...) wie bereits erwähnt. Wie immer mit C ++: Wenn du es nicht tust - du wirst dir wünschen, dass du es getan hast ...

Eine weitere gute Übung für Klassen ist es, die Konstruktion und Zuweisung von Kopien privat zu machen (auch als "disable it"), es sei denn, Sie müssen sie wirklich implementieren. Dies vermeidet mögliche Kopien von Zeigern, wenn Sie die Methoden verwenden, die C ++ standardmäßig für Sie erstellt. Eine andere Möglichkeit, dies zu tun, stammt von boost :: noncopyable.




Konstruktoren fügen eine implizite Konvertierung hinzu. Um diese implizite Konvertierung zu unterdrücken, muss ein Konstruktor mit einem expliziten Parameter deklariert werden.

In C ++ 11 können Sie auch einen "operator type ()" mit einem solchen Schlüsselwort angeben: here Mit dieser Spezifikation können Sie den Operator in Form von expliziten Konvertierungen und direkte Initialisierung des Objekts.

PS Wenn Transformationen verwendet werden, die von BENUTZER definiert sind (über Konstruktoren und Typkonvertierungsoperatoren), darf nur eine Ebene der impliziten Konvertierungen verwendet werden. Sie können diese Conversions jedoch mit anderen Sprachkonvertierungen kombinieren

  • up integral ranks (char zu int, float zu double);
  • Standardkonvertierungen (int zu double);
  • Zeiger von Objekten in Basisklasse konvertieren und void *;



Das Schlüsselwort explicit begleitet beides

  • Ein Konstruktor der Klasse X, mit dem der erste (beliebige) Parameter nicht implizit in den Typ X konvertiert werden kann

C ++ [Klasse.conv.ctor]

1) Ein Konstruktor, der ohne den expliziten Funktionsspezifizierer deklariert wurde, spezifiziert eine Konvertierung von den Typen seiner Parameter in den Typ seiner Klasse. Ein solcher Konstruktor wird Konvertierungskonstruktor genannt.

2) Ein expliziter Konstruktor konstruiert Objekte genauso wie nicht-explizite Konstruktoren, aber nur dort, wo die direkte Initialisierungssyntax (8.5) oder wo Umwandlungen (5.2.9, 5.4) explizit verwendet werden. Ein Standardkonstruktor kann ein expliziter Konstruktor sein; Ein solcher Konstruktor wird verwendet, um eine Standard-Initialisierung oder -Wert-Initialisierung (8.5) durchzuführen.

  • oder eine Konvertierungsfunktion, die nur für direkte Initialisierung und explizite Konvertierung in Betracht gezogen wird.

C ++ [Klasse.conv.fct]

2) Eine Konvertierungsfunktion kann explizit sein (7.1.2), in diesem Fall wird sie nur als eine benutzerdefinierte Konvertierung für die direkte Initialisierung (8.5) betrachtet. Andernfalls sind benutzerdefinierte Konvertierungen nicht auf Zuweisungen und Initialisierungen beschränkt.

Überblick

Explizite Konvertierungsfunktionen und Konstruktoren können nur für explizite Konvertierungen (direkte Initialisierung oder explizite Umwandlungsoperation) verwendet werden, während nicht explizite Konstruktoren und Konvertierungsfunktionen sowohl für implizite als auch explizite Konvertierungen verwendet werden können.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

Beispiel mit Strukturen X, Y, Z und Funktionen foo, bar, baz :

Lassen Sie uns einen kleinen Aufbau von Strukturen und Funktionen betrachten, um den Unterschied zwischen explicit und nicht- explicit Konvertierungen zu erkennen.

struct Z { };

struct X { 
  explicit X(int a); // X can be constructed from int explicitly
  explicit operator Z (); // X can be converted to Z explicitly
};

struct Y{
  Y(int a); // int can be implicitly converted to Y
  operator Z (); // Y can be implicitly converted to Z
};

void foo(X x) { }
void bar(Y y) { }
void baz(Z z) { }

Beispiele bezüglich Konstruktor:

Konvertierung eines Funktionsarguments:

foo(2);                     // error: no implicit conversion int to X possible
foo(X(2));                  // OK: direct initialization: explicit conversion
foo(static_cast<X>(2));     // OK: explicit conversion

bar(2);                     // OK: implicit conversion via Y(int) 
bar(Y(2));                  // OK: direct initialization
bar(static_cast<Y>(2));     // OK: explicit conversion

Objektinitialisierung:

X x2 = 2;                   // error: no implicit conversion int to X possible
X x3(2);                    // OK: direct initialization
X x4 = X(2);                // OK: direct initialization
X x5 = static_cast<X>(2);   // OK: explicit conversion 

Y y2 = 2;                   // OK: implicit conversion via Y(int)
Y y3(2);                    // OK: direct initialization
Y y4 = Y(2);                // OK: direct initialization
Y y5 = static_cast<Y>(2);   // OK: explicit conversion

Beispiele zu Konvertierungsfunktionen:

X x1{ 0 };
Y y1{ 0 };

Konvertierung eines Funktionsarguments:

baz(x1);                    // error: X not implicitly convertible to Z
baz(Z(x1));                 // OK: explicit initialization
baz(static_cast<Z>(x1));    // OK: explicit conversion

baz(y1);                    // OK: implicit conversion via Y::operator Z()
baz(Z(y1));                 // OK: direct initialization
baz(static_cast<Z>(y1));    // OK: explicit conversion

Objektinitialisierung:

Z z1 = x1;                  // error: X not implicitly convertible to Z
Z z2(x1);                   // OK: explicit initialization
Z z3 = Z(x1);               // OK: explicit initialization
Z z4 = static_cast<Z>(x1);  // OK: explicit conversion

Z z1 = y1;                  // OK: implicit conversion via Y::operator Z()
Z z2(y1);                   // OK: direct initialization
Z z3 = Z(y1);               // OK: direct initialization
Z z4 = static_cast<Z>(y1);  // OK: explicit conversion

Warum explicit Konvertierungsfunktionen oder Konstruktoren verwenden?

Konvertierungskonstruktoren und nicht-explizite Konvertierungsfunktionen können Mehrdeutigkeiten einführen.

Man betrachte eine Struktur V , die in int umsetzbar ist, eine Struktur U implizit aus V konstruiert werden kann, und eine Funktion f für U bzw. bool überladen ist.

struct V {
  operator bool() const { return true; }
};

struct U { U(V) { } };

void f(U) { }
void f(bool) {  }

Ein Aufruf von f ist mehrdeutig, wenn ein Objekt vom Typ V .

V x;
f(x);  // error: call of overloaded 'f(V&)' is ambiguous

Der Compiler weiß nicht, ob er den Konstruktor von U oder die Konvertierungsfunktion verwenden soll, um das V Objekt in einen Typ umzuwandeln, der an f .

Wenn entweder der Konstruktor von U oder die Konvertierungsfunktion von V explicit , gäbe es keine Mehrdeutigkeit, da nur die nicht-explizite Umwandlung in Betracht gezogen würde. Wenn beide explizit sind, müsste der Aufruf von f Verwendung eines Objekts vom Typ V unter Verwendung einer expliziten Konvertierungs- oder Umwandlungsoperation durchgeführt werden.

Konvertierungskonstruktoren und nicht explizite Konvertierungsfunktionen können zu unerwartetem Verhalten führen.

Betrachten Sie eine Funktion, die einen Vektor druckt:

void print_intvector(std::vector<int> const &v) { for (int x : v) std::cout << x << '\n'; }

Wenn der Größenkonstruktor des Vektors nicht explizit wäre, wäre es möglich, die Funktion wie folgt aufzurufen:

print_intvector(3);

Was würde man von einem solchen Anruf erwarten? Eine Zeile mit 3 oder 3 Zeilen enthält 0 ? (Wo die zweite ist, was passiert.)

Die Verwendung des expliziten Schlüsselworts in einer Klassenschnittstelle erzwingt, dass der Benutzer der Schnittstelle explizit über eine gewünschte Konvertierung spricht.

Wie Bjarne Stroustrup es (in "Die C ++ Programmiersprache", 4. Aufl., 35.2.1, S. 1011) zur Frage stellt, warum std::duration nicht implizit aus einer einfachen Zahl konstruiert werden kann:

Wenn du weißt, was du meinst, sei klar darüber.