Question Que signifie le mot clé explicite?


Que fait le explicit mot-clé signifie en C ++?


2363
2017-09-23 13:58


origine


Réponses:


Le compilateur est autorisé à effectuer une conversion implicite pour résoudre les paramètres d'une fonction. Ce que cela signifie est que le compilateur peut utiliser des constructeurs appelables avec un un seul paramètre convertir d'un type à un autre afin d'obtenir le bon type pour un paramètre.

Voici un exemple de classe avec un constructeur pouvant être utilisé pour les conversions implicites:

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

Voici une fonction simple qui prend un Foo objet:

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

et voici où le DoBar La fonction est appelée.

int main ()
{
  DoBar (42);
}

L'argument n'est pas Foo objet, mais un int. Cependant, il existe un constructeur pour Foo cela prend un int donc ce constructeur peut être utilisé pour convertir le paramètre au type correct.

Le compilateur est autorisé à le faire une fois pour chaque paramètre.

Préfixer le explicit mot-clé au constructeur empêche le compilateur d'utiliser ce constructeur pour les conversions implicites. L'ajouter à la classe ci-dessus va créer une erreur de compilation lors de l'appel de fonction DoBar (42). Il est maintenant nécessaire d'appeler explicitement à la conversion DoBar (Foo (42))

La raison pour laquelle vous pourriez vouloir faire ceci est d'éviter une construction accidentelle qui peut cacher des bogues. Exemple contraint:

  • Tu as un MyString(int size) class avec un constructeur qui construit une chaîne de la taille donnée. Vous avez une fonction print(const MyString&)et vous appelez print(3) (lorsque vous réellement destiné à appeler print("3")). Vous vous attendez à imprimer "3", mais il imprime une chaîne vide de longueur 3 à la place.

2749
2017-09-23 14:09



Supposons que vous ayez une classe String:

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

Maintenant, si vous essayez:

String mystring = 'x';

Le personnage 'x' sera implicitement converti en int puis le String(int) le constructeur sera appelé. Mais, ce n'est pas ce que l'utilisateur aurait pu vouloir. Donc, pour éviter de telles conditions, nous allons définir le constructeur comme 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



En C ++, un constructeur avec un seul paramètre requis est considéré comme une fonction de conversion implicite. Il convertit le type de paramètre en type de classe. Que ce soit une bonne chose ou non dépend de la sémantique du constructeur.

Par exemple, si vous avez une classe de chaînes avec un constructeur String(const char* s), c'est probablement exactement ce que vous voulez. Vous pouvez passer un const char* à une fonction qui attend un String, et le compilateur construira automatiquement un temporaire String objet pour vous.

D'un autre côté, si vous avez une classe tampon dont le constructeur Buffer(int size) prend la taille du tampon en octets, vous ne voulez probablement pas que le compilateur tourne tranquillement ints dans Buffers. Pour éviter cela, vous déclarez le constructeur avec le explicit mot-clé:

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

De cette façon,

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

devient une erreur de compilation. Si vous voulez passer un temporaire Buffer objet, vous devez le faire explicitement:

useBuffer(Buffer(4));

En résumé, si votre constructeur à un seul paramètre convertit le paramètre en un objet de votre classe, vous ne voulez probablement pas utiliser le explicit mot-clé. Mais si vous avez un constructeur qui prend simplement un seul paramètre, vous devez le déclarer comme explicitpour empêcher le compilateur de vous surprendre avec des conversions inattendues.


130
2017-10-08 14:43



Cette réponse concerne la création d'objet avec / sans constructeur explicite car elle n'est pas couverte dans les autres réponses.

Considérons la classe suivante sans constructeur explicite:

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

private:
    int m_x;
};

Les objets de la classe Foo peuvent être créés de deux façons:

Foo bar1(10);

Foo bar2 = 20;

Selon la mise en œuvre, la deuxième manière d'instancier la classe Foo peut être déroutante, ou pas ce que le programmeur a voulu. Préfixer le explicit mot-clé au constructeur générerait une erreur de compilation à Foo bar2 = 20;.

C'est d'habitude bonne pratique pour déclarer les constructeurs à un seul argument explicit, à moins que votre implémentation ne l'interdise spécifiquement.

Notez également que les constructeurs avec

  • arguments par défaut pour tous les paramètres, ou
  • arguments par défaut pour le deuxième paramètre à partir de

Les deux peuvent être utilisés comme constructeurs à un seul argument. Donc, vous pouvez vouloir les faire aussi explicit.

Un exemple quand vous le feriez délibérément ne pas vouloir rendre explicite le constructeur à un seul argument si vous créez un foncteur (regardez la structure 'add_x' déclarée dans ce répondre). Dans ce cas, créer un objet comme add_x add30 = 30; aurait probablement du sens.

Ici est un bon article sur les constructeurs explicites.


34
2017-11-21 02:36



le explicit Le mot-clé fait un constructeur de conversion au constructeur de non-conversion. Par conséquent, le code est moins sujet aux erreurs.


31
2017-07-10 23:48



Le mot-clé explicit accompagne soit

  • un constructeur de classe X qui ne peut pas être utilisé pour convertir implicitement le premier (uniquement) paramètre en type X

C ++ [class.conv.ctor]

1) Un constructeur déclaré sans le spécificateur de fonction explicite spécifie une conversion des types de ses paramètres au type de sa classe. Un tel constructeur est appelé un constructeur de conversion.

2) Un constructeur explicite construit des objets comme des constructeurs non-explicites, mais ne le fait que lorsque la syntaxe d'initialisation directe (8.5) ou les casts (5.2.9, 5.4) sont explicitement utilisés. Un constructeur par défaut peut être un constructeur explicite; un tel constructeur sera utilisé pour effectuer une initialisation par défaut ou une initialisation de valeur   (8.5).

  • ou une fonction de conversion qui n'est considérée que pour l'initialisation directe et la conversion explicite.

C ++ [class.conv.fct]

2) Une fonction de conversion peut être explicite (7.1.2), auquel cas elle n'est considérée que comme une conversion définie par l'utilisateur pour l'initialisation directe (8.5). Sinon, les conversions définies par l'utilisateur ne sont pas limitées à l'utilisation dans les affectations   et initialisations.

Aperçu

Les fonctions de conversion explicites et les constructeurs ne peuvent être utilisés que pour des conversions explicites (initialisation directe ou opération de conversion explicite) tandis que les constructeurs non-explicites et les fonctions de conversion peuvent être utilisés pour des conversions implicites ou explicites.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

Exemple utilisant des structures X, Y, Z et fonctions foo, bar, baz:

Regardons une petite configuration de structures et de fonctions pour voir la différence entre explicitet nonexplicit conversions

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

Exemples concernant le constructeur:

Conversion d'un argument de fonction:

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

Initialisation de l'objet

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

Exemples concernant les fonctions de conversion:

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

Conversion d'un argument de fonction:

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

Initialisation de l'objet

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

Pourquoi utiliser explicit fonctions de conversion ou constructeurs?

Les constructeurs de conversion et les fonctions de conversion non explicites peuvent introduire une ambiguïté.

Envisager une structure V, convertible en int, une structure U implicitement constructible de V et une fonction f surchargé pour U et bool respectivement.

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

struct U { U(V) { } };

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

Un appel à f est ambigu si vous passez un objet de type V.

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

Le compilateur ne sait pas utiliser le constructeur de U ou la fonction de conversion pour convertir le V objet dans un type pour passer à f.

Si soit le constructeur de U ou la fonction de conversion de V serait explicit, il n'y aurait pas d'ambiguïté puisque seule la conversion non-explicite serait considérée. Si les deux sont explicites, l'appel à f en utilisant un objet de type V devrait être fait en utilisant une conversion explicite ou une opération de cast.

Les constructeurs de conversion et les fonctions de conversion non explicites peuvent conduire à un comportement inattendu.

Considérons une fonction imprimant un vecteur:

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

Si le constructeur de taille du vecteur ne serait pas explicite, il serait possible d'appeler la fonction comme ceci:

print_intvector(3);

Qu'attend-on d'un tel appel? Une ligne contenant 3 ou trois lignes contenant 0? (Où le second est ce qui se passe.)

L'utilisation du mot clé explicite dans une interface de classe impose à l'utilisateur de l'interface d'être explicite sur une conversion souhaitée.

Comme le dit Bjarne Stroustrup (dans "The C ++ Programming Language", 4ème Ed., 35.2.1, pp. 1011) sur la question de savoir pourquoi std::duration ne peut pas être implicitement construit à partir d'un nombre simple:

Si vous savez ce que vous voulez dire, soyez explicite à ce sujet.


31
2018-05-14 09:28



le explicit-keyword peut être utilisé pour appliquer un constructeur à appeler explicitement.

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

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

la explicit-keyword devant le constructeur C(void) indique au compilateur que seul l'appel explicite à ce constructeur est autorisé.

le explicit-keyword peut également être utilisé dans les opérateurs de cast de type défini par l'utilisateur:

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

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

Ici, explicit-keyword impose uniquement des conversions explicites pour être valide, donc bool b = c; serait une distribution invalide dans ce cas. Dans des situations comme celles-ci explicit-keyword peut aider le programmeur à éviter les lancers implicites et involontaires. Cet usage a été standardisé dans C ++ 11.


25
2017-10-01 22:00



Cela a déjà été discuté (Qu'est-ce qu'un constructeur explicite). Mais je dois dire, qu'il manque les descriptions détaillées trouvées ici.

En outre, c'est toujours une bonne pratique de codage de faire vos constructeurs d'un seul argument (y compris ceux avec des valeurs par défaut pour arg2, arg3, ...) comme déjà indiqué. Comme toujours avec C ++: si vous ne le faites pas - vous aurez envie de le faire ...

Une autre bonne pratique pour les classes est de rendre la construction et l'assignation des copies privées (par exemple désactiver) à moins que vous n'ayez vraiment besoin de l'implémenter. Cela évite d'avoir éventuellement des copies de pointeurs lorsque vous utilisez les méthodes que C ++ va créer pour vous par défaut. Une autre façon de procéder est dérivée de boost :: noncopyable.


17
2017-08-20 12:45



Cpp Référence est toujours utile !!! Des détails sur le spécificateur explicite peuvent être trouvés ici. Vous devrez peut-être regarder conversions implicites et copie-initialisation aussi.

Coup d'oeil

Le spécificateur explicite spécifie qu'un constructeur ou une fonction de conversion (depuis C ++ 11) n'autorise pas les conversions implicites ou l'initialisation de la copie.

Exemple comme suit:

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