it-swarm-eu.dev

Co znamená explicitní klíčové slovo?

Co znamená slovo explicit v C++?

2605
Skizz

Kompilátoru je povoleno provést jednu implicitní konverzi k vyřešení parametrů funkce. To znamená, že kompilátor může použít konstruktory s volitelným parametrem jeden parametr pro převod z jednoho typu na druhý, aby získal správný typ parametru.

Zde je příklad třídy s konstruktorem, který lze použít pro implicitní konverze:

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

Zde je jednoduchá funkce, která má objekt Foo:

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

a zde je místo, kde je volána funkce DoBar.

int main ()
{
  DoBar (42);
}

Argument není objekt Foo, ale int. Existuje však konstruktor pro Foo, který má int, takže tento konstruktor může být použit k převodu parametru na správný typ.

Kompilátor to může provést jednou pro každý parametr.

Předpona klíčového slova explicit do konstruktoru zabraňuje kompilátoru používat tento konstruktor pro implicitní konverze. Přidání do výše uvedené třídy vytvoří chybu kompilátoru při volání funkce DoBar (42). Nyní je nutné explicitně volat k převodu pomocí DoBar (Foo (42))

Důvod, proč to chcete udělat, je vyhnout se náhodné konstrukci, která může skrýt chyby. Příklad v rozporu:

  • Máte třídu MyString(int size) s konstruktorem, který konstruuje řetězec dané velikosti. Máte funkci print(const MyString&) a zavoláte print(3) (když vlastně zamýšlíte zavolat print("3")). Očekáváte, že bude tisknout "3", ale místo toho vytiskne prázdný řetězec o délce 3.
3001
Skizz

Předpokládejme, že máte třídu String:

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

Pokud se pokusíte:

String mystring = 'x';

Znak 'x' bude implicitně převeden na int a potom bude vyvolán konstruktor String(int). Ale to není to, co uživatel zamýšlel. Abychom takovým podmínkám zabránili, definujeme konstruktor jako explicit:

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

V C++ je konstruktor s pouze jedním požadovaným parametrem považován za implicitní funkci konverze. Převádí typ parametru na typ třídy. Zda to je dobrá věc nebo ne, závisí na sémantice konstruktoru.

Pokud máte například třídu řetězců s konstruktorem String(const char* s), je to pravděpodobně přesně to, co chcete. const char* můžete předat funkci, která očekává String, a kompilátor pro vás automaticky vytvoří dočasný objekt String.

Na druhou stranu, pokud máte třídu vyrovnávacích pamětí, jejíž konstruktor Buffer(int size) bere velikost vyrovnávací paměti v bytech, pravděpodobně nechcete, aby kompilátor v tichosti zapnul ints do Buffers. Chcete-li tomu zabránit, deklarujte konstruktor klíčovým slovem explicit:

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

Tímto způsobem

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

se stane chyba kompilace. Pokud chcete předat dočasný objekt Buffer, musíte tak učinit explicitně:

useBuffer(Buffer(4));

Stručně řečeno, pokud váš konstruktor s jedním parametrem převede parametr na objekt vaší třídy, pravděpodobně nechcete použít klíčové slovo explicit. Máte-li však konstruktor, který se jednoduše stane jedním parametrem, měli byste jej deklarovat jako explicit, abyste zabránili tomu, aby vás kompilátor překvapil neočekávanými konverzemi.

145
cjm

Tato odpověď je o vytvoření objektu s/bez explicitního konstruktoru, protože není obsažena v ostatních odpovědích.

Zvažte následující třídu bez explicitního konstruktoru:

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

private:
    int m_x;
};

Objekty třídy Foo lze vytvořit dvěma způsoby:

Foo bar1(10);

Foo bar2 = 20;

V závislosti na implementaci může být druhý způsob iniciační třídy Foo matoucí, nebo ne to, co zamýšlel programátor. Předpona klíčového slova explicit do konstruktoru by generovala chybu kompilátoru na Foo bar2 = 20;.

Je to obvykle dobrá praxe deklarovat konstruktéry s jedním argumentem jako explicit, pokud to vaše implementace výslovně nezakazuje.

Všimněte si také, že konstruktéři s

  • výchozí argumenty pro všechny parametry, nebo
  • výchozí argumenty pro druhý parametr

lze použít jako konstruktéry s jedním argumentem. Možná budete chtít, aby tyto explicit.

Příklad, kdy byste záměrněnechcete, aby byl váš konstruktor jednoho argumentu explicitní, pokud vytváříte funktor (podívejte se na strukturu 'add_x' deklarovanou v to Odpovědět). V takovém případě by vytvoření objektu jako add_x add30 = 30; pravděpodobně mělo smysl.

Zde je dobrý zápis na explicitní konstruktory.

39
Gautam

Klíčové slovo explicit vytváří konstruktor převodu na konstruktor bez převodu. Výsledkem je, že kód je méně náchylný k chybám.

37
SankararaoMajji

Klíčové slovo explicit doprovází

  • konstruktor třídy X, který nelze použít k implicitnímu převodu prvního (libovolného) parametru na typ X

C++ [class.conv.ctor]

1) Konstruktor deklarovaný bez explicitně specifikujícího funkce specifikuje převod z typů jeho parametrů na typ své třídy. Takový konstruktor se nazývá konstruktor konvertování.

2) Explicitní konstruktor konstruuje objekty stejně jako neexplicitní konstruktory, ale dělá pouze tam, kde je explicitně použita syntaxe přímé inicializace (8.5) nebo kde jsou obsazení (5.2.9, 5.4) explicitně použita. Výchozí konstruktor může být explicitní konstruktor; takový konstruktor bude použit k provedení výchozí inicializace nebo valueinitializace (8.5).

  • nebo konverzní funkce, která je uvažována pouze pro přímou inicializaci a explicitní převod.

C++ [class.conv.fct]

2) Konverzní funkce může být explicitní (7.1.2), v tomto případě se považuje pouze za uživatelem definovanou konverzi pro přímou inicializaci (8.5). V opačném případě nejsou uživatelem definované konverze omezeny na použití v přiřazeních a inicializacích.

Přehled

Explicitní konverzní funkce a konstruktory mohou být použity pouze pro explicitní konverze (operace přímé inicializace nebo explicitní obsazení), zatímco pro implicitní i explicitní konverze mohou být použity explicitní konstruktory a konverzní funkce.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

Příklad použití struktur X, Y, Z a funkcí foo, bar, baz:

Podívejme se na malé nastavení struktur a funkcí, abychom viděli rozdíl mezi převody explicit a non -explicit.

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

Příklady týkající se konstruktoru:

Převod argumentu funkce:

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

Inicializace objektu:

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

Příklady týkající se funkcí převodu:

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

Převod argumentu funkce:

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

Inicializace objektu:

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

Proč používat explicit konverzní funkce nebo konstruktory?

Konverzní konstruktory a non-explicitní konverzní funkce mohou představovat nejednoznačnost.

Zvažte strukturu V, konvertibilní na int, strukturu U implicitně konstruovatelnou z V a funkci f přetíženou pro U a bool resp.

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

struct U { U(V) { } };

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

Volání f je nejednoznačné, pokud předáte objekt typu V.

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

Kompilátor neví, zda použít konstruktor U nebo konverzní funkci k převodu objektu V na typ pro předání f.

Pokud by konstruktor U nebo konverzní funkce V byl explicit, neexistovala by žádná dvojznačnost, protože by byla zvažována pouze explicitní konverze. Pokud jsou obě explicitní, volání f pomocí objektu typu V by muselo být provedeno pomocí explicitní operace konverze nebo obsazení.

Konverzní konstrukty a neexportní konverzní funkce mohou vést k neočekávanému chování.

Zvažte funkci tisku nějakého vektoru:

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

Pokud by konstruktor velikosti vektoru nebyl explicitní, bylo by možné tuto funkci zavolat takto:

print_intvector(3);

Co by člověk od takového volání očekával? Jeden řádek obsahující 3 nebo tři řádky obsahující 0? (Kde je druhá věc.)

Použití explicitní klíčové slovo v rozhraní třídy vynutí uživatel rozhraní musí být explicitní o požadované převodu.

Jak říká Bjarne Stroustrup (v "The C++ Programming Language", 4. vydání, 35.2.1, str. 1011) na otázku, proč std::duration nemůže být implicitně konstruováno z prostého čísla:

Pokud víte, co tím myslíte, buďte k tomu výslovní.

36
Pixelchemist

Klíčové slovo explicit- lze použít k vynucení konstruktoru, který má být volán explicitně .

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

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

explicit- klíčové slovo před konstruktorem C(void) řekne kompilátoru, že je povoleno pouze explicitní volání tohoto konstruktoru.

Klíčové slovo explicit- lze také použít v uživatelsky definovaných operátorech typu cast:

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

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

Zde explicit- klíčových slov vynucuje pouze explicitní obsazení, které mají být platné, takže bool b = c; by v tomto případě bylo neplatné obsazení. V situacích, jako jsou tyto explicit- klíčové slovo může pomoci programátor, aby se zabránilo implicitní, nezamýšlené obsazení. Toto použití bylo standardizováno v C++ 11 .

26
Helixirr

Explicitní konstrukty konverze (pouze C++)

Explicitní specifikátor funkce řídí nechtěné konverze implicitního typu. Lze jej použít pouze v deklaracích konstruktorů v rámci deklarace třídy. Například, s výjimkou výchozího konstruktoru, konstruktory v následující třídě jsou konstrukty konverze.

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

Následující prohlášení jsou legální:

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

První deklarace je ekvivalentní A c = A( 1 );.

Pokud deklarujete konstruktor třídy jako explicit, předchozí deklarace budou nelegální.

Pokud například deklarujete třídu jako:

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

Můžete přiřadit pouze hodnoty, které odpovídají hodnotám typu třídy.

Následující prohlášení jsou například legální:

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

To již bylo diskutováno ( co je explicitní konstruktor ). Ale musím říci, že zde chybí podrobné popisy.

Kromě toho je vždy dobrou praxí kódování, aby se váš jeden argument konstruktér (včetně těch s výchozími hodnotami pro arg2, arg3, ...), jak již bylo uvedeno. Jako vždy s C++: pokud ne - budete chtít, abyste ...

Dalším dobrým postupem pro výuku je vytvořit kopii stavby a přiřazení soukromého (a.k.a.automatizovat ji), pokud ji opravdu nepotřebujete implementovat. Tím se vyhnete případným kopiím ukazatelů při použití metod, které bude ve výchozím nastavení C++ vytvářet. Jiný způsob, jak toho dosáhnout, je odvozen od boost :: noncopyable.

18
fmuecke

Cpp Reference je vždy užitečné !!! Podrobnosti o explicitním specifikátoru lze nalézt zde . Možná se budete muset podívat na implicitní konverze a kopírování-inicializace také.

Rychlý pohled

Explicitní specifikátor určuje, že konstruktor nebo konverzní funkce (protože C++ 11) neumožňuje implicitní konverze nebo inicializaci kopírování.

Příklad:

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
}
18
selfboot

Konstruktory připojují implicitní konverzi. Pro potlačení této implicitní konverze je nutné deklarovat konstruktor s explicitním parametrem.

V C++ 11 můžete také zadat "typ operátora ()" s takovým klíčovým slovem http://en.cppreference.com/w/cpp/language/explicit S takovou specifikací můžete použít operátora v termínech explicitní konverze a přímá inicializace objektu.

P.S. Při použití transformací definovaných BY USER (pomocí konstruktorů a operátorů typu konverze) je povolena pouze jedna úroveň implicitních konverzí. Tyto konverze však můžete kombinovat s jinými konverzemi jazyka

  • do integrálních řad (char na int, float na double);
  • standardní převody (int na double);
  • převést ukazatele objektů na základní třídu a zrušit *;
5
bruziuz