web-dev-qa-db-de.com

Was bedeutet das explizite Schlüsselwort?

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

2678
Skizz

Der Compiler darf eine implizite Konvertierung durchführen, um die Parameter in eine Funktion aufzulösen. Dies bedeutet, dass der Compiler Konstruktoren verwenden kann, die mit einem einzelnen Parameter aufrufbar sind, 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
{
public:
  // single parameter constructor, can be used as an implicit conversion
  Foo (int foo) : m_foo (foo) 
  {
  }

  int GetFoo () { return m_foo; }

private:
  int m_foo;
};

Hier ist eine einfache Funktion, die ein Foo -Objekt annimmt:

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

und hier wird die Funktion DoBar aufgerufen.

int main ()
{
  DoBar (42);
}

Das Argument ist kein Foo Objekt, sondern ein int. Es gibt jedoch einen Konstruktor für Foo, der int akzeptiert, sodass dieser Konstruktor zum Konvertieren des Parameters in den richtigen Typ verwendet werden kann.

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

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

Möglicherweise möchten Sie dies tun, um versehentliche Konstruktionen zu vermeiden, die Fehler verbergen können. Erfundenes Beispiel:

  • Sie haben eine MyString(int size) -Klasse mit einem Konstruktor, der eine Zeichenfolge der angegebenen Größe erstellt. Sie haben eine Funktion print(const MyString&), und Sie rufen print(3) auf (wenn Sie tatsächlich beabsichtigt haben, print("3") aufzurufen). Sie erwarten, dass es "3" druckt, aber es druckt stattdessen eine leere Zeichenfolge der Länge 3.
3085
Skizz

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 anschließend der Konstruktor String(int) aufgerufen. Dies ist jedoch möglicherweise nicht die Absicht des Benutzers. 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
};
1065
Eddie

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

Wenn Sie beispielsweise eine Zeichenfolgenklasse mit dem Konstruktor String(const char* s) haben, ist dies wahrscheinlich genau das, was Sie wollen. Sie können const char* an eine Funktion übergeben, die String erwartet, und der Compiler erstellt automatisch ein temporäres String -Objekt für Sie.

Wenn Sie dagegen eine Pufferklasse haben, deren Konstruktor Buffer(int size) die Größe des Puffers in Bytes annimmt, möchten Sie wahrscheinlich nicht, dass der Compiler ints in Buffers umwandelt. Um dies zu verhindern, deklarieren Sie den Konstruktor mit dem Schlüsselwort explicit:

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

Dieser Weg,

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

wird zu einem Fehler beim Kompilieren. Wenn Sie ein temporäres Buffer -Objekt übergeben möchten, müssen Sie dies explizit tun:

useBuffer(Buffer(4));

Zusammenfassend gesagt, wenn Ihr Konstruktor mit nur einem Parameter den Parameter in ein Objekt Ihrer Klasse konvertiert, möchten Sie wahrscheinlich das Schlüsselwort explicit nicht verwenden. Wenn Sie jedoch einen Konstruktor haben, der zufällig einen einzelnen Parameter akzeptiert, sollten Sie ihn als explicit deklarieren, um zu verhindern, dass der Compiler Sie mit unerwarteten Konvertierungen überrascht.

149
cjm

Diese Antwort bezieht sich auf die Objekterstellung mit/ohne expliziten 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 zwei Arten erstellt werden:

Foo bar1(10);

Foo bar2 = 20;

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

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

Beachten Sie auch, dass Konstruktoren mit

  • standardargumente für alle Parameter oder
  • standardargumente ab dem zweiten Parameter

können beide als Konstruktoren mit einem Argument verwendet werden. Sie können diese also auch explicit machen.

Ein Beispiel, bei dem Sie absichtlich nicht Ihren Konstruktor mit einem Argument explizit angeben möchten, ist, wenn Sie Erstellen Sie einen Funktor (sehen Sie sich die in this answer deklarierte 'add_x'-Struktur an). In einem solchen Fall wäre es wahrscheinlich sinnvoll, ein Objekt als add_x add30 = 30; zu erstellen.

Hier ist eine gute Zusammenfassung zu expliziten Konstruktoren.

39
Gautam

Mit dem Schlüsselwort explicit wird ein Konvertierungskonstruktor in einen Nicht-Konvertierungskonstruktor umgewandelt. Infolgedessen ist der Code weniger fehleranfällig.

37
SankararaoMajji

Das Schlüsselwort explicit wird ebenfalls verwendet

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

C++ [class.conv.ctor]

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

2) Ein expliziter Konstruktor erstellt Objekte wie nicht-explizite Konstruktoren, jedoch nur, wenn die Syntax für die direkte Initialisierung (8.5) oder Casts (5.2.9, 5.4) explizit verwendet werden. Ein Standardkonstruktor kann ein expliziter Konstruktor sein. Ein solcher Konstruktor wird verwendet, um eine Standardinitialisierung oder eine Werteinitialisierung durchzuführen (8.5).

  • oder eine Konvertierungsfunktion, die nur für die direkte Initialisierung und explizite 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. Andernfalls sind benutzerdefinierte Konvertierungen nicht auf die Verwendung in 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 für implizite und 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 nichtexplicit 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 zum 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 für 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 zu Mehrdeutigkeiten führen.

Betrachten Sie eine Struktur V, die in int konvertierbar ist, eine Struktur U, die implizit aus V konstruiert werden kann, und eine Funktion f, die für U und bool.

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

struct U { U(V) { } };

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

Ein Aufruf von f ist nicht eindeutig, wenn ein Objekt vom Typ V übergeben wird.

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 Objekt V in einen Typ für die Übergabe an f zu konvertieren.

Wenn entweder der Konstruktor von U oder die Konvertierungsfunktion von Vexplicit wäre, würde es keine Mehrdeutigkeit geben, da nur die nicht explizite Konvertierung berücksichtigt würde. Wenn beide explizit sind, muss der Aufruf von f mit einem Objekt vom Typ V mit einer expliziten Konvertierungs- oder Umwandlungsoperation erfolgen.

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 drei Zeilen mit 0? (Wo der zweite ist, was passiert.)

Die Verwendung des Schlüsselworts explicit in einer Klassenschnittstelle erzwingt, dass der Benutzer der Schnittstelle eine gewünschte Konvertierung explizit ausführt.

Wie Bjarne Stroustrup es ausdrückt (in "The C++ Programming Language", 4. Auflage, 35.2.1, S. 1011), kann std::duration nicht implizit aus einer einfachen Zahl konstruiert werden:

Wenn Sie wissen, was Sie meinen, sagen Sie es ausdrücklich.

36
Pixelchemist

Explizite Konvertierungskonstruktoren (nur C++)

Der explizite Funktionsspezifizierer steuert unerwünschte implizite Typkonvertierungen. Es kann nur in Deklarationen von Konstruktoren innerhalb einer Klassendeklaration verwendet werden. Mit Ausnahme des Standardkonstruktors sind die Konstruktoren in der folgenden Klasse beispielsweise Konvertierungskonstruktoren.

class A
{
public:
    A();
    A(int);
    A(const char*, int = 0);
};

Die folgenden Erklärungen sind legal:

A c = 1;
A d = "Venditti";

Die erste Deklaration entspricht A c = A( 1 );.

Wenn Sie den Konstruktor der Klasse als explicit deklarieren, sind die vorherigen Deklarationen ungültig.

Wenn Sie beispielsweise die Klasse als deklarieren:

class A
{
public:
    explicit A();
    explicit A(int);
    explicit A(const char*, int = 0);
};

Sie können nur Werte zuweisen, die den Werten der Klassenart entsprechen.

Beispielsweise sind die folgenden Aussagen zulässig:

  A a1;
  A a2 = A(1);
  A a3(1);
  A a4 = A("Venditti");
  A* p = new A(1);
  A a5 = (A)1;
  A a6 = static_cast<A>(1);
35
coding_ninza

Mit dem Schlüsselwort explicit- kann erzwungen werden, dass ein Konstruktor explizit aufgerufen wird .

class C{
public:
    explicit C(void) = default;
};

int main(void){
    C c();
    return 0;
}

das explicit- Schlüsselwort vor dem Konstruktor C(void) teilt dem Compiler mit, dass nur ein expliziter Aufruf dieses Konstruktors zulässig ist.

Das Schlüsselwort explicit- kann auch in benutzerdefinierten Typumwandlungsoperatoren verwendet werden:

class C{
public:
    explicit inline operator bool(void) const{
        return true;
    }
};

int main(void){
    C c;
    bool b = static_cast<bool>(c);
    return 0;
}

Hier erzwingt das Schlüsselwort explicit-, dass nur explizite Umwandlungen gültig sind, sodass bool b = c; in diesem Fall eine ungültige Umwandlung wäre. In Situationen wie diesen kann das explicit - Schlüsselwort dem Programmierer helfen, implizite, unbeabsichtigte Besetzungen zu vermeiden. Diese Verwendung wurde in C++ 11 standardisiert.

27
Helixirr

CPP-Referenz ist immer hilfreich! Details zum expliziten Bezeichner finden Sie hier . Möglicherweise müssen Sie auch implizite Konvertierungen und Kopierinitialisierung betrachten.

Schneller Blick

Der explizite Bezeichner gibt an, dass ein Konstruktor oder eine Konvertierungsfunktion (seit C++ 11) keine impliziten Konvertierungen oder Kopierinitialisierungen zulässt.

Beispiel wie folgt:

struct A
{
    A(int) { }      // converting constructor
    A(int, int) { } // converting constructor (C++11)
    operator bool() const { return true; }
};

struct B
{
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};

int main()
{
    A a1 = 1;      // OK: copy-initialization selects A::A(int)
    A a2(2);       // OK: direct-initialization selects A::A(int)
    A a3 {4, 5};   // OK: direct-list-initialization selects A::A(int, int)
    A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
    A a5 = (A)1;   // OK: explicit cast performs static_cast
    if (a1) cout << "true" << endl; // OK: A::operator bool()
    bool na1 = a1; // OK: copy-initialization selects A::operator bool()
    bool na2 = static_cast<bool>(a1); // OK: static_cast performs direct-initialization

//  B b1 = 1;      // error: copy-initialization does not consider B::B(int)
    B b2(2);       // OK: direct-initialization selects B::B(int)
    B b3 {4, 5};   // OK: direct-list-initialization selects B::B(int, int)
//  B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
    B b5 = (B)1;   // OK: explicit cast performs static_cast
    if (b5) cout << "true" << endl; // OK: B::operator bool()
//  bool nb1 = b2; // error: copy-initialization does not consider B::operator bool()
    bool nb2 = static_cast<bool>(b2); // OK: static_cast performs direct-initialization
}
19
selfboot

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

Außerdem ist es immer eine gute Codierungspraxis, Ihre Konstruktoren mit einem Argument (einschließlich derjenigen mit Standardwerten für arg2, arg3, ...) wie bereits angegeben zu erstellen. Wie immer mit C++: Wenn Sie dies nicht tun, werden Sie es wünschen ...

Eine weitere bewährte Methode für Klassen besteht darin, die Erstellung und Zuweisung von Kopien als privat zu definieren (und zu deaktivieren), es sei denn, Sie müssen sie wirklich implementieren. Dadurch wird vermieden, dass eventuelle Kopien von Zeigern vorhanden sind, wenn Sie die Methoden verwenden, die C++ standardmäßig für Sie erstellt. Eine andere Möglichkeit ist boost :: noncopyable.

18
fmuecke

Konstruktoren hängen implizite Konvertierung an. Um diese implizite Konvertierung zu unterdrücken, muss ein Konstruktor mit einem expliziten Parameter deklariert werden.

In C++ 11 können Sie mit diesem Schlüsselwort auch einen "operator type ()" angeben http://en.cppreference.com/w/cpp/language/explicit Mit dieser Angabe können Sie operator verwenden in Bezug auf explizite Konvertierungen und direkte Initialisierung des Objekts.

P.S. Bei Verwendung von Transformationen, die von USER definiert wurden (über Konstruktoren und Typkonvertierungsoperatoren), ist nur eine Ebene impliziter Konvertierungen zulässig. Sie können diese Konvertierungen jedoch mit anderen Sprachkonvertierungen kombinieren

  • auf ganzzahligen Rängen (char bis int, float bis double);
  • standardkonvertierungen (int zu double);
  • zeiger von Objekten in Basisklasse umwandeln und ungültig machen *;
5
bruziuz