Was bedeutet das ausdrückliche Stichwort? [c++]


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

Jetzt, wenn du es versuchst

String mystring = 'x';

Das char 'x' wird implizit in int umgewandelt und dann wird der String (int) Konstruktor aufgerufen. Aber das ist nicht das, was der Benutzer hätte beabsichtigen können. Um solche Bedingungen zu verhindern, werden 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

Jemand hat in einem Kommentar zu einer anderen Frage über die Bedeutung des explicit Keyword in C ++ geschrieben. Also, was bedeutet das?




Diese Antwort bezieht sich auf die Objekterstellung mit / ohne einen expliziten Konstruktor, da sie nicht in den anderen Antworten abgedeckt ist.

Betrachten Sie die folgende Klasse ohne einen expliziten Konstruktor:

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

private:
    int m_x;
};

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

Foo bar1(10);

Foo bar2 = 20;

Je nach Implementierung kann die zweite Art der Instanziierung der Klasse Foo verwirrend sein oder nicht, was der Programmierer beabsichtigt hat. Das Präfixieren des explicit Schlüsselworts auf den Konstruktor würde einen Compiler-Fehler bei Foo bar2 = 20; erzeugen Foo bar2 = 20; .

Es ist in der Regel eine gute Praxis, Einzel-Argument-Konstruktoren als explicit zu deklarieren, es sei denn, dass Ihre Implementierung es spezifisch verbietet.

Beachten Sie auch, dass Konstruktoren mit

  • Standardargumente für alle Parameter oder
  • Standard-Argumente für den zweiten Parameter

Können beide als Einzel-Argument-Konstruktoren verwendet werden. Also möchtest du das auch explicit .

Ein Beispiel, wenn Sie bewusst nicht möchten, dass Ihr Single-Argument-Konstruktor explizit ist, wenn Sie einen Funktor erstellen (siehe das Add_x 'struct, das in dieser Antwort deklariert wurde). In solch einem Fall, das Erstellen eines Objekts als add_x add30 = 30; Wäre wohl sinnvoll.

Hier ist eine gute Zuschreibung auf explizite Konstrukteure.




Das Schlüsselwort explicit begleitet entweder

  • Ein Konstruktor der Klasse X, der nicht verwendet werden kann, um implizit den ersten (beliebigen) Parameter in den Typ X umzuwandeln

C ++ [class.conv.ctor]

1) Ein Konstruktor, der ohne den Funktions-Spezifizierer explizit deklariert wird, spezifiziert eine Umwandlung von den Typen seiner Parameter zum Typ seiner Klasse. Ein solcher Konstruktor wird als konvertierender Konstruktor bezeichnet.

2) Ein expliziter Konstruktor konstruiert Objekte wie nicht explizite Konstruktoren, aber nur dort, wo die Direktinitialisierungssyntax (8.5) oder wo Casts (5.2.9, 5.4) explizit verwendet werden. Ein Standardkonstruktor kann ein expliziter Konstruktor sein. Ein solcher Konstruktor wird verwendet, um eine Default-Initialisierung oder eine Valueinitialisierung durchzuführen (8.5).

  • Oder eine Umwandlungsfunktion, die nur für die direkte Initialisierung und explizite Umwandlung berücksichtigt wird.

C ++ [class.conv.fct]

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

Überblick

Explizite Umwandlungsfunktionen und Konstruktoren können nur für explizite Konvertierungen (direkte Initialisierung oder explizite Gussoperation) verwendet werden, während nicht explizite Konstruktoren und Umwandlungsfunktionen sowohl 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 :

Schauen wir uns einen kleinen Aufbau von Strukturen und Funktionen an, um den Unterschied zwischen explicit und nicht explicit Konvertierungen zu sehen.

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 für Konstrukteur:

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 für Umwandlungsfunktionen:

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 eine Mehrdeutigkeit einführen.

Man betrachte eine Struktur V , die in int umwandelbar ist, eine aus U implizit konstruierbare Struktur U und eine für U bzw. bool überladene Funktion f .

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

struct U { U(V) { } };

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

Ein Aufruf von f ist zweideutig, wenn man ein Objekt vom Typ V übergibt.

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

Der Compiler weiß nicht, ob der Konstruktor von U oder die Umwandlungsfunktion verwendet werden soll, um das V Objekt in einen Typ umzuwandeln, um an f .

Wenn entweder der Konstruktor von U oder die Umwandlungsfunktion von V explicit , würde es keine Unklarheit geben, da nur die nicht explizite Umwandlung berücksichtigt würde. Wenn beide explizit sind, müsste der Aufruf von f Verwendung eines Objekts vom Typ V mit einer expliziten Umwandlung oder einem gegossenen Vorgang erfolgen.

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

Betrachten Sie eine Funktion Druck einige Vektor:

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 drei Zeilen mit 0 ? (Wo das 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 ist.

Wie Bjarne Stroustrup ausführt (in "The C ++ Programming Language", 4. Aufl., 35.2.1, S. 1011) auf die Frage, warum std::duration nicht implizit aus einer einfachen Nummer aufgebaut werden kann:

Wenn du weißt, was du meinst, sei es explizit.




Dies wurde bereits diskutiert ( was ist expliziter Konstruktor ). Aber ich muss sagen, dass es an den hier beschriebenen detaillierten Beschreibungen fehlt

Außerdem ist es immer eine gute Codierungspraxis, um Ihre ein Argument-Konstruktoren (einschließlich der mit Standardwerten für arg2, arg3, ...) wie bereits erwähnt zu machen. Wie immer mit C ++: wenn du es nicht tust - du wirst es wünschen ...

Eine weitere gute Praxis für Klassen ist es, Kopie Bau und Zuweisung private (aka deaktivieren), wenn Sie wirklich brauchen, um es zu implementieren. Dies vermeidet eventuelle Kopien von Zeigern bei der Verwendung der Methoden, die C ++ standardmäßig für Sie erstellt. Eine andere Möglichkeit, dies zu tun, ergibt sich aus dem Boost :: noncopyable.




Konstrukteure hängen implizite Umwandlung an. Um diese implizite Umwandlung zu unterdrücken, muss man einen Konstruktor mit einem expliziten Parameter deklarieren.

In C ++ 11 können Sie auch einen "Betreibertyp ()" mit einem solchen Schlüsselwort angeben. Http://de.cppreference.com/w/cpp/language/explicit Mit dieser Spezifikation können Sie den Betreiber in Form von expliziten Conversions verwenden und Direkte Initialisierung des Objekts.

PS Bei Verwendung von Transformationen, die von BENUTZER (über Konstruktoren und Typumwandlungsoperator) definiert sind, ist nur eine Stufe von impliziten Umwandlungen erlaubt. Aber du kannst diese Umwandlungen mit anderen Sprachkonvertierungen kombinieren

  • Up-Integral-Reihen (char zu int, float to double);
  • Standart conversions (int zu verdoppeln);
  • Umwandlung von Objekten in die Basisklasse und auf void *;