3653 Stimmen

Was bedeutet das Schlüsselwort explicit?

Was bedeutet die explicit Schlüsselwort in C++ bedeuten?

228 Stimmen

Ich möchte nur jeden, der neu hinzukommt, darauf hinweisen, dass seit C++11, explicit kann nicht nur auf Konstruktoren angewendet werden. Es gilt jetzt auch für Konvertierungsoperatoren. Angenommen, Sie haben eine Klasse BigInt mit einem Umwandlungsoperator in int und einen expliziten Umwandlungsoperator in std::string aus welchem Grund auch immer. Sie werden sagen können int i = myBigInt; aber Sie müssen explizit einen Cast durchführen (mit static_cast , vorzugsweise), um zu sagen std::string s = myBigInt; .

2 Stimmen

Kann man sich nicht auch explizit auf den Auftrag beziehen? (d.h.. int x(5); )

0 Stimmen

@chris Die Idee einer expliziten impliziten Umwandlung ist absurd. Lassen Sie die Finger davon!

4157voto

Skizz Punkte 66931

Der Compiler darf eine implizite Umwandlung vornehmen, um die Parameter in eine Funktion aufzulösen. Das bedeutet, dass der Compiler Konstruktoren verwenden kann, die mit einer einziger Parameter um von einem Typ in einen anderen zu konvertieren, um den richtigen Typ für einen Parameter zu erhalten.

Hier ist eine Beispielklasse mit einem Konstruktor, der für implizite Konvertierungen verwendet werden kann:

class Foo
{
private:
  int m_foo;

public:
  // single parameter constructor, can be used as an implicit conversion
  Foo (int foo) : m_foo (foo) {}

  int GetFoo () { return m_foo; }
};

Hier ist eine einfache Funktion, die eine Foo Objekt:

void DoBar (Foo foo)
{
  int i = foo.GetFoo ();
}

und hier ist die DoBar Funktion aufgerufen wird:

int main ()
{
  DoBar (42);
}

Das Argument ist kein Foo Objekt, sondern ein int . Es gibt jedoch einen Konstruktor für Foo die eine int daher kann dieser Konstruktor verwendet werden, um den Parameter in den richtigen Typ zu konvertieren.

Der Compiler darf dies für jeden Parameter einmal tun.

Das Voranstellen der explicit Schlüsselwort an den Konstruktor verhindert, dass der Compiler diesen Konstruktor für implizite Konvertierungen verwendet. Das Hinzufügen des Schlüsselworts zur obigen Klasse führt zu einem Compilerfehler beim Funktionsaufruf DoBar (42) . Es ist nun notwendig, die Konvertierung explizit aufzurufen mit DoBar (Foo (42))

Der Grund, warum Sie dies tun sollten, ist, dass Sie versehentliche Konstruktionen vermeiden wollen, in denen sich Wanzen verstecken können.
Ein erfundenes Beispiel:

  • Sie haben eine MyString Klasse mit einem Konstruktor, der eine Zeichenkette der angegebenen Größe konstruiert. Sie haben eine Funktion print(const MyString&) (sowie eine Überlastung print (char *string) ), und Sie rufen print(3) (wenn Sie eigentlich beabsichtigt, anrufen print("3") ). Sie erwarten, dass er "3" ausgibt, aber er gibt stattdessen eine leere Zeichenkette der Länge 3 aus.

252 Stimmen

Netter Bericht, Sie sollten vielleicht erwähnen, dass Multi-Arg-Ctors mit Standardparametern auch als Single-Arg-Ctor fungieren können, z. B. Object( const char* name=NULL, int otype=0).

582 Stimmen

Ich denke, es sollte auch erwähnt werden, dass man in Erwägung ziehen sollte, Konstruktoren mit einem Argument zunächst explizit zu machen (mehr oder weniger automatisch) und das explizite Schlüsselwort nur dann zu entfernen, wenn die implizite Konvertierung erwünscht ist konstruktionsbedingt . Ich denke, Konstruktoren sollten standardmäßig explizit sein, mit einem "implicit"-Schlüsselwort, damit sie als implizite Konvertierungen arbeiten können. Aber das ist nicht der Fall.

12 Stimmen

@thecoshman: Man deklariert nicht eine Parameter explicit -- Sie deklarieren eine Konstrukteur explicit . Aber ja: Ihre Parameter vom Typ Foo müssen konstruiert werden explicite len, werden sie nicht stillschweigend konstruiert, indem man einfach die Parameter ihres Konstruktors in die Funktion einfügt.

1337voto

Eddie Punkte 12625

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

Wenn Sie es jetzt versuchen:

String mystring = 'x';

Das Zeichen 'x' wird implizit umgewandelt in int und dann die String(int) Konstruktor aufgerufen werden. Dies ist jedoch nicht das, 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
};

18 Stimmen

Und es ist erwähnenswert, dass die neuen verallgemeinerten Initialisierungsregeln von C++0x die String s = {0}; schlecht geformt, anstatt zu versuchen, den anderen Konstruktor mit einem Null-Zeiger aufzurufen, wie String s = 0; tun würde.

11 Stimmen

Auch wenn es sich um eine alte Frage handelt, scheint es sich zu lohnen, auf ein paar Dinge hinzuweisen (oder jemanden zu bitten, mich aufzuklären). Indem man die int-Form oder beide Koren "explizit" macht, würde man immer noch den gleichen Fehler haben, wenn man String mystring('x') wenn Sie meinten String mystring("x") Oder etwa nicht? Außerdem entnehme ich dem obigen Kommentar das verbesserte Verhalten von String s = {0} über String s = 0 dank der 'expliziten' Verwendung der int-Form des ctor. Aber abgesehen davon, dass Sie den Vorrang der Ctors kennen, wie können Sie die Absicht (d.h. wie Sie den Fehler erkennen) dieses String s{0} ?

1 Stimmen

Warum wird String mystring = 'x'; in int umgewandelt?

201voto

cjm Punkte 60581

In C++ wird ein Konstruktor mit nur einem erforderlichen Parameter als implizite Konvertierungsfunktion betrachtet. Sie konvertiert den Parametertyp in den Klassentyp. Ob dies eine gute Sache ist oder nicht, hängt von der Semantik des Konstruktors ab.

Wenn Sie zum Beispiel eine String-Klasse mit dem Konstruktor String(const char* s) ist das wahrscheinlich genau das, was Sie wollen. Sie können eine const char* zu einer Funktion, die eine String und der Compiler konstruiert automatisch eine temporäre String Objekt für Sie.

Andererseits, wenn Sie eine Pufferklasse haben, deren Konstruktor Buffer(int size) die Größe des Puffers in Bytes angibt, wollen Sie wahrscheinlich nicht, dass der Compiler stillschweigend in int s in Buffer s. Um dies zu verhindern, deklarieren Sie den Konstruktor mit der explicit Stichwort:

class Buffer { explicit Buffer(int size); ... }

Auf diese Weise,

void useBuffer(Buffer& buf);
useBuffer(4);

wird zu einem Kompilierzeitfehler. Wenn Sie eine temporäre Buffer Objekt, müssen Sie dies explizit tun:

useBuffer(Buffer(4));

Zusammenfassend lässt sich sagen, dass Sie, wenn Ihr Ein-Parameter-Konstruktor den Parameter in ein Objekt Ihrer Klasse umwandelt, wahrscheinlich nicht die explicit Stichwort. Wenn Sie jedoch einen Konstruktor haben, der nur einen einzigen Parameter benötigt, sollten Sie ihn als explicit um zu verhindern, dass der Compiler Sie mit unerwarteten Konvertierungen überrascht.

11 Stimmen

useBuffer erwartet einen l-Wert für sein Argument, useBuffer(Buffer(4)) wird auch deshalb nicht funktionieren. Ändern Sie es zu nehmen eine const Buffer& o Buffer&& oder einfach Buffer würde es funktionieren.

67voto

Pixelchemist Punkte 22777

Das Schlüsselwort explicit begleitet entweder

  • ein Konstruktor der Klasse X, der nicht verwendet werden kann, um den ersten (oder einzigen) Parameter implizit in den Typ X zu konvertieren

C++ [class.conv.ctor]

1) Ein Konstruktor, der ohne expliziten Funktionsspezifizierer deklariert wird, gibt eine Konvertierung von den Typen seiner Parameter in den Typ seiner Klasse an. Ein solcher Konstruktor wird als konvertierender Konstruktor bezeichnet.

2) Ein expliziter Konstruktor konstruiert Objekte genauso wie nicht-explizite Konstruktoren, tut dies aber nur, wenn die Syntax der direkten Initialisierung (8.5) oder Casts (5.2.9, 5.4) explizit verwendet werden. Ein Standard-Konstruktor kann ein expliziter Konstruktor sein; ein solcher Konstruktor wird verwendet, um eine Standard-Initialisierung oder Wert-Initialisierung durchzuführen (8.5).

  • oder eine Konvertierungsfunktion, die nur bei direkter Initialisierung und expliziter Konvertierung berücksichtigt wird.

C++ [class.conv.fct]

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

Übersicht

Explizite Konvertierungsfunktionen und Konstruktoren können nur für explizite Konvertierungen verwendet werden (direkte Initialisierung oder explizite Cast-Operation), während nicht-explizite Konstruktoren und Konvertierungsfunktionen sowohl für implizite als auch für 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 Umrechnungen.

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 zum Konstruktor:

Umwandlung 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

Objekt-Initialisierung:

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 Konvertierungsfunktionen:

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

Umwandlung 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

Objekt-Initialisierung:

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 verwenden explicit Konvertierungsfunktionen oder Konstruktoren?

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

Betrachten Sie eine Struktur V , umwandelbar in int eine Struktur U implizit konstruierbar aus V und eine Funktion f überlastet für U y bool beziehungsweise.

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

struct U { U(V) { } };

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

Ein Aufruf zur f ist zweideutig, 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 zur Umwandlung der V Objekt in einen Typ zur Übergabe an f .

Wenn entweder der Konstruktor von U oder die Umrechnungsfunktion von V sería explicit würde es keine Unklarheiten geben, da nur die nicht explizite Umwandlung berücksichtigt würde. Wenn beide explizit sind, wird der Aufruf von f mit einem Objekt des Typs V müsste eine explizite Konvertierung oder ein Casting 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, könnte man die Funktion so aufrufen:

print_intvector(3);

Was würde man von einem solchen Anruf erwarten? Eine Zeile mit 3 oder drei Zeilen mit 0 ? (Wobei das zweite das ist, was passiert.)

Die Verwendung des expliziten Schlüsselworts in einer Klassenschnittstelle zwingt den Benutzer der Schnittstelle, eine gewünschte Konvertierung explizit anzugeben.

Wie Bjarne Stroustrup (in "The C++ Programming Language", 4th Ed., 35.2.1, pp. 1011) auf die Frage, warum std::duration kann nicht stillschweigend aus einer einfachen Zahl konstruiert werden:

Wenn Sie wissen, was Sie meinen, sollten Sie es explizit sagen.

1 Stimmen

"Wenn du weißt, was du meinst, dann sag es auch ganz deutlich. Was für ein tolles Zitat. Erinnere mich noch einmal daran, warum es Auto gibt :-)

59voto

Gautam Punkte 959

In dieser Antwort geht es um die Erstellung von Objekten mit/ohne expliziten Konstruktor, da dies in den anderen Antworten nicht behandelt wird.

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 2 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 dem Willen des Programmierers entsprechen. Das Voranstellen der explicit Schlüsselwort in den Konstruktor würde einen Compilerfehler bei Foo bar2 = 20; .

Sie ist in der Regel gute Praxis, Konstruktoren mit einem Argument als explicit Es sei denn, Ihre Implementierung verbietet dies ausdrücklich.

Beachten Sie auch, dass Konstruktoren mit

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

können beide als Ein-Argument-Konstruktoren verwendet werden. Sie können diese also auch als explicit .

Ein Beispiel, wenn Sie absichtlich no Wenn Sie einen Konstruktor mit einem einzigen Argument explizit machen wollen, müssen Sie einen Funktor erstellen (siehe die Struktur "add_x", die in diese Antwort). In einem solchen Fall ist die Erstellung eines Objekts als add_x add30 = 30; würde wahrscheinlich Sinn machen.

Hier ist ein guter Artikel über explizite Konstruktoren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X