Vraag Wat betekent het expliciete zoekwoord?


Wat doet de explicit zoekwoord betekenen in C ++?


2363
2017-09-23 13:58


oorsprong


antwoorden:


De compiler mag één impliciete conversie uitvoeren om de parameters naar een functie op te lossen. Wat dit betekent is dat de compiler constructors kan gebruiken die ophaalbaar zijn met een enkele parameter om van het ene type naar het andere te converteren om het juiste type voor een parameter te krijgen.

Hier is een voorbeeldklasse met een constructor die kan worden gebruikt voor impliciete conversies:

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 is een eenvoudige functie die een Foo voorwerp:

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

en hier is waar de DoBar functie wordt aangeroepen.

int main ()
{
  DoBar (42);
}

Het argument is geen Foo object, maar een int. Er bestaat echter een constructor voor Foo dat kost een int dus deze constructor kan worden gebruikt om de parameter naar het juiste type om te zetten.

De compiler mag dit één keer doen voor elke parameter.

Voorvoegen van de explicit sleutelwoord voor de constructor voorkomt dat de compiler die constructor gebruikt voor impliciete conversies. Als u het aan de bovenstaande klasse toevoegt, wordt er een compileerfout gemaakt bij de functieaanroep DoBar (42). Het is nu noodzakelijk om expliciet te bellen voor conversie met DoBar (Foo (42))

De reden dat u dit zou willen doen, is het vermijden van onbedoelde constructies die bugs kunnen verbergen. Ingepast voorbeeld:

  • Je hebt een MyString(int size) klasse met een constructor die een reeks van de opgegeven grootte construeert. Je hebt een functie print(const MyString&)en je belt print(3) (wanneer je werkelijk bedoeld om te bellen print("3")). U verwacht dat het "3" zal afdrukken, maar het drukt in plaats daarvan een lege reeks van lengte 3 af.

2749
2017-09-23 14:09



Stel dat je een klas hebt String:

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

Nu, als je het probeert:

String mystring = 'x';

Het personage 'x' wordt impliciet geconverteerd naar int en dan de String(int) constructor wordt gebeld. Maar dit is niet wat de gebruiker misschien heeft bedoeld. Dus om dergelijke omstandigheden te voorkomen, zullen we de constructor definiëren als explicit:

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

962
2017-09-23 16:37



In C ++ wordt een constructor met slechts één vereiste parameter beschouwd als een impliciete conversiefunctie. Het converteert het parameter type naar het klasse type. Of dit goed is of niet hangt af van de semantiek van de constructor.

Bijvoorbeeld als u een stringklasse met constructor hebt String(const char* s), dat is waarschijnlijk precies wat je wilt. Je kunt een const char* naar een functie die een verwacht String, en de compiler maakt automatisch een tijdelijke constructie String object voor jou.

Aan de andere kant, als u een bufferklasse hebt waarvan de constructor Buffer(int size) neemt de grootte van de buffer in bytes, je wilt waarschijnlijk niet dat de compiler stil wordt ints in Buffers. Om dit te voorkomen, verklaart u de constructeur met de explicit trefwoord:

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

Op die manier,

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

wordt een compileerfout. Als je een tijdelijke wilt passeren Buffer object, moet je dit expliciet doen:

useBuffer(Buffer(4));

Samenvattend, als je constructor met één parameter de parameter converteert naar een object van je klasse, wil je waarschijnlijk niet de explicit trefwoord. Maar als u een constructor hebt die eenvoudigweg één parameter neemt, moet u dit als declareren explicitom te voorkomen dat de compiler je verrast met onverwachte conversies.


130
2017-10-08 14:43



Dit antwoord gaat over het maken van objecten met / zonder een expliciete constructor, omdat dit niet wordt behandeld in de andere antwoorden.

Beschouw de volgende klasse zonder een expliciete constructor:

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

private:
    int m_x;
};

Objecten van klasse Foo kunnen op twee manieren worden gemaakt:

Foo bar1(10);

Foo bar2 = 20;

Afhankelijk van de implementatie, kan de tweede manier om klasse Foo in te stellen verwarrend zijn, of niet wat de programmeur bedoelde. Voorvoegen van de explicit sleutelwoord voor de constructor zou een compileerfout genereren op Foo bar2 = 20;.

Het is doorgaans goede praktijken om constructies met één argument te declareren als explicit, tenzij uw implementatie het specifiek verbiedt.

Merk ook op dat constructeurs met

  • standaardargumenten voor alle parameters, of
  • standaardargumenten voor de tweede parameter en verder

kunnen beide worden gebruikt als constructors met één argument. Dus misschien wil je deze ook maken explicit.

Een voorbeeld wanneer u het bewust zou doen niet wilt dat uw constructor met één argument expliciet is als u een functor maakt (kijk naar de 'add_x' struct die in deze antwoord). In een dergelijk geval maakt u een object als add_x add30 = 30; zou waarschijnlijk logisch zijn.

Hier is een goed artikel over expliciete constructeurs.


34
2017-11-21 02:36



De explicit keyword maakt een conversie-constructor naar constructor zonder conversie. Als gevolg hiervan is de code minder foutgevoelig.


31
2017-07-10 23:48



Het sleutelwoord explicit begeleidt een van beide

  • een constructor van klasse X die niet kan worden gebruikt om de eerste (enige enige) parameter impliciet in type X te converteren

C ++ [class.conv.ctor]

1) Een constructor die is gedeclareerd zonder de functiespecificatie explicit specificeert een conversie van de typen parameters naar het type van zijn klasse. Zo'n constructor wordt een conversieconstructor genoemd.

2) Een expliciete constructor construeert objecten net als niet-expliciete constructors, maar doet dit alleen wanneer de syntaxis van de directe initialisatie (8.5) of waar casts (5.2.9, 5.4) expliciet worden gebruikt. Een standaardconstructor kan een expliciete constructor zijn; zo'n constructor zal worden gebruikt om standaard-initialisatie of waarde-initialisatie uit te voeren   (8.5).

  • of een conversiefunctie die alleen wordt overwogen voor directe initialisatie en expliciete conversie.

C ++ [class.conv.fct]

2) Een conversiefunctie kan expliciet zijn (7.1.2), in welk geval deze alleen wordt beschouwd als een door de gebruiker gedefinieerde conversie voor directe initialisatie (8.5). Anders zijn door de gebruiker gedefinieerde conversies niet beperkt tot gebruik in toewijzingen   en initialisaties.

Overzicht

Expliciete conversiefuncties en constructors kunnen alleen worden gebruikt voor expliciete conversies (directe initialisatie of expliciete cast-bewerking), terwijl niet-expliciete constructeurs en conversiefuncties kunnen worden gebruikt voor zowel impliciete als expliciete conversies.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

Voorbeeld met behulp van structuren X, Y, Z en functies foo, bar, baz:

Laten we eens kijken naar een kleine set van structuren en functies om het verschil tussen te zien expliciten nietexplicit conversies.

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) { }

Voorbeelden met betrekking tot constructor:

Conversie van een functieargument:

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

Object initialisatie:

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

Voorbeelden met betrekking tot conversiefuncties:

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

Conversie van een functieargument:

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

Object initialisatie:

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

Waarom gebruiken explicit conversiefuncties of constructeurs?

Conversie-constructors en niet-expliciete conversiefuncties kunnen dubbelzinnigheid introduceren.

Overweeg een structuur V, converteerbaar naar int, een structuur U impliciet geconstrueerd uit V en een functie f overbelast voor U en bool respectievelijk.

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

struct U { U(V) { } };

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

Een oproep aan f is dubbelzinnig bij het passeren van een object van het type V.

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

De compiler weet niet of de constructor van moet worden gebruikt U of de conversiefunctie om de V object in een type voor doorgeven aan f.

Als de constructeur van U of de conversiefunctie van V zou zijn explicit, er zou geen dubbelzinnigheid zijn, omdat alleen de niet-expliciete conversie zou worden overwogen. Als beide expliciet de oproep zijn naar f een object van het type gebruiken V zou moeten worden gedaan met behulp van een expliciete conversie of cast-bewerking.

Conversieconstructeurs en niet-expliciete conversiefuncties kunnen leiden tot onverwacht gedrag.

Overweeg een functie die een vector afdrukt:

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

Als de grootte-constructor van de vector niet expliciet zou zijn, zou het mogelijk zijn om de functie als volgt aan te roepen:

print_intvector(3);

Wat zou men van zo'n oproep verwachten? Eén regel met 3 of drie regels met 0? (Waar de tweede is wat er gebeurt.)

Het gebruik van het expliciete sleutelwoord in een klasse-interface dwingt de gebruiker van de interface om expliciet te zijn over een gewenste conversie.

Zoals Bjarne Stroustrup het zegt (in "The C ++ Programming Language", 4th Ed., 35.2.1, pp. 1011) over de vraag waarom std::duration kan niet impliciet worden opgebouwd uit een gewoon getal:

Als je begrijpt wat je bedoelt, wees er dan expliciet over.


31
2018-05-14 09:28



De explicit-keyword kan worden gebruikt om een ​​aan te roepen constructor af te dwingen uitdrukkelijk.

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

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

de explicit-zoekwoord voor de constructor C(void) vertelt de compiler dat alleen een expliciete aanroep naar deze constructor is toegestaan.

De explicit-keyword kan ook worden gebruikt in door de gebruiker gedefinieerde type cast-operators:

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, explicit-word dwingt alleen expliciete casts om geldig te zijn, dus bool b = c; zou in dit geval een ongeldige cast zijn. In situaties als deze explicit-keyword kan de programmeur helpen om impliciete, onbedoelde afgietsels te voorkomen. Dit gebruik is gestandaardiseerd in C ++ 11.


25
2017-10-01 22:00



Dit is al besproken (wat is een expliciete constructor). Maar ik moet zeggen dat het de gedetailleerde beschrijvingen ontbeert die hier te vinden zijn.

Bovendien is het altijd een goede codeermethode om je enige argumentconstructors (inclusief die met standaardwaarden voor arg2, arg3, ...) te maken zoals eerder vermeld. Zoals altijd met C ++: als je dat niet doet, zou je willen dat je het deed ...

Een andere goede gewoonte voor klassen is om de constructie en de toewijzing van de kopie privé te maken (a.k.a. uit te schakelen), tenzij je het echt moet implementeren. Hiermee wordt voorkomen dat er kopieën van aanwijzers worden gemaakt bij het gebruik van de methoden die C ++ standaard voor u maakt. Een andere manier om dit te doen is afgeleid van boost :: noncopyable.


17
2017-08-20 12:45



Cpp-referentie is altijd nuttig !!! Details over expliciete specificatie zijn te vinden hier. U moet misschien naar kijken impliciete conversies en copy-initialisatie te.

Snelle blik

De expliciete specificatie geeft aan dat een constructor of conversiefunctie (sinds C ++ 11) geen impliciete conversies of initialisatie van de kopie toestaat.

Voorbeeld als volgt:

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
}

15
2017-12-20 12:19



Expliciete conversie-constructors (alleen C ++)

De expliciete functie-specificatie bepaalt het ongewenste impliciete type   conversies. Het kan alleen worden gebruikt in verklaringen van constructeurs   binnen een klassenverklaring. Bijvoorbeeld, behalve de standaardwaarde   constructor, de constructeurs in de volgende klasse zijn conversie   constructeurs.

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

De volgende verklaringen zijn legaal:

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

De eerste verklaring is gelijk aan A c = A( 1 );.

Als u de constructor van de klasse als declareert explicit, de eerdere verklaringen zouden illegaal zijn.

Als u bijvoorbeeld de klasse declareert als:

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

U kunt alleen waarden toewijzen die overeenkomen met de waarden van het klassentype.

De volgende verklaringen zijn bijvoorbeeld legaal:

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

9
2018-01-23 09:26