web-dev-qa-db-fra.com

Que signifie le mot clé explicite?

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

2678
Skizz

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

Voici un exemple de classe avec un constructeur pouvant être utilisé pour des 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 objet Foo:

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

et voici où la fonction DoBar est appelée.

int main ()
{
  DoBar (42);
}

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

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

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

La raison pour laquelle vous voudrez peut-être faire cela est d'éviter une construction accidentelle qui pourrait cacher des bugs. Exemple élaboré:

  • Vous avez une classe MyString(int size) 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 avez réellement prévu d'appeler print("3")). Vous vous attendez à ce qu'il affiche "3", mais une chaîne vide de longueur 3 est imprimée à la place.
3085
Skizz

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 caractère 'x' sera implicitement converti en int, puis le constructeur String(int) 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
};
1065
Eddie

En C++, un constructeur avec un seul paramètre obligatoire 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îne avec le 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 objet String temporaire pour vous.

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

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 objet temporaire Buffer, vous devez le faire explicitement:

useBuffer(Buffer(4));

En résumé, si votre constructeur à paramètre unique convertit le paramètre en objet de votre classe, vous ne souhaiterez probablement pas utiliser le mot clé explicit. Mais si vous avez un constructeur qui prend simplement un paramètre, vous devez le déclarer comme explicit pour éviter que le compilateur ne vous surprenne avec des conversions inattendues.

149
cjm

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érez la classe suivante sans constructeur explicite:

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

private:
    int m_x;
};

Les objets de classe Foo peuvent être créés de 2 manières:

Foo bar1(10);

Foo bar2 = 20;

En fonction de l'implémentation, la deuxième manière d'instancier la classe Foo peut être source de confusion ou ne pas correspondre à l'intention du programmeur. Le préfixage du mot clé explicit au constructeur générerait une erreur de compilation à Foo bar2 = 20;.

Il est généralement préférable de déclarer les constructeurs à un seul argument comme étant 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 second paramètre

peuvent tous deux être utilisés en tant que constructeurs à argument unique. Donc, vous voudrez peut-être aussi faire ceci explicit.

Un exemple où vous voudriez délibérément ne pas rendre votre constructeur d'argument unique explicite si vous ' Recréer un foncteur (regardez la structure 'add_x' déclarée dans this answer). Dans un tel cas, créer un objet comme add_x add30 = 30; aurait probablement un sens.

Here est une bonne description des constructeurs explicites.

39
Gautam

Le mot clé explicit transforme un constructeur de conversion en constructeur de non-conversion. En conséquence, le code est moins sujet aux erreurs.

37
SankararaoMajji

Le mot clé explicit accompagne soit

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

C++ [class.conv.ctor]

1) Un constructeur déclaré sans le spécificateur de fonction explicit spécifie une conversion des types de ses paramètres vers le type de sa classe. Un tel constructeur s'appelle un constructeur de conversion.

2) Un constructeur explicite construit des objets de la même manière que des constructeurs non explicites, mais uniquement lorsque la syntaxe d'initialisation directe (8.5) ou les casts (5.2.9, 5.4) sont utilisés explicitement. 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 est uniquement prise en compte 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 est uniquement considérée comme une conversion définie par l'utilisateur pour une initialisation directe (8.5). Sinon, les conversions définies par l'utilisateur ne sont pas limitées à une utilisation dans les affectations et les initialisations.

Vue d'ensemble

Les fonctions et les constructeurs de conversion explicites ne peuvent être utilisés que pour les 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 les conversions implicites et explicites.

/*
                                 explicit conversion          implicit conversion

 explicit constructor                    yes                          no

 constructor                             yes                          yes

 explicit conversion function            yes                          no

 conversion function                     yes                          yes

*/

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

Examinons un petit ensemble de structures et de fonctions pour voir la différence entre les conversions explicit et les 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) { }

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 ou constructeurs de conversion?

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

Considérons une structure V, convertible en int, une structure U implicitement constructible à partir de V et une fonction f surchargée 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 transmettez un objet de type V.

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

Le compilateur ne sait pas s'il faut utiliser le constructeur de U ou la fonction de conversion pour convertir l'objet V en un type permettant de passer à f.

Si le constructeur de U ou la fonction de conversion de V était explicit, il n'y aurait aucune ambiguïté, car seule la conversion non explicite serait prise en compte. Si les deux sont explicites, l'appel à f à l'aide d'un objet de type V devrait être effectué à l'aide d'une conversion explicite ou d'une opération de conversion.

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'attendrait-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é explicit dans une interface de classe oblige l'utilisateur de l'interface à être explicite à propos de la conversion souhaitée.

Comme le dit Bjarne Stroustrup (dans "Le langage de programmation C++", 4e éd., 35.2.1, p. 1011) sur la question de savoir pourquoi std::duration ne peut pas être construit implicitement à partir d'un nombre simple:

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

36
Pixelchemist

Constructeurs de conversion explicites (C++ uniquement)

Le spécificateur de fonction explicite contrôle les conversions de types implicites indésirables. Il ne peut être utilisé que dans les déclarations de constructeurs d'une déclaration de classe. Par exemple, à l'exception du constructeur par défaut, les constructeurs de la classe suivante sont des constructeurs de conversion.

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

Les déclarations suivantes sont légales:

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

La première déclaration est équivalente à A c = A( 1 );.

Si vous déclarez le constructeur de la classe en tant que explicit, les déclarations précédentes seraient illégales.

Par exemple, si vous déclarez la classe en tant que:

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

Vous ne pouvez affecter que des valeurs correspondant aux valeurs du type de classe.

Par exemple, les déclarations suivantes sont légales:

  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

Le mot clé explicit- peut être utilisé pour imposer à un constructeur d'être appelé explicitement .

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

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

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

Le mot clé explicit- peut également être utilisé dans les opérateurs de transtypage définis 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, le mot clé explicit- n'applique que les conversions explicites à la validité. Par conséquent, bool b = c; serait une distribution non valide dans ce cas. Dans de telles situations, le mot clé explicit- peut aider le programmeur à éviter les conversions implicites et involontaires. Cet usage a été normalisé dans C++ 11 .

27
Helixirr

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

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 ni l'initialisation de 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
}
19
selfboot

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

En outre, il est toujours bon de coder vos constructeurs à un seul argument (y compris ceux avec des valeurs par défaut pour arg2, arg3, ...), comme indiqué précédemment. Comme toujours avec C++: si vous ne le faites pas, vous souhaiterez le faire ...

Une autre bonne pratique pour les classes consiste à rendre la construction de la copie et l’affectation privées (a.k.a. la désactiver), sauf si vous avez vraiment besoin de l’implémenter. Cela évite d'avoir des copies éventuelles de pointeurs lors de l'utilisation des méthodes que C++ créera pour vous par défaut. Une autre façon de faire est d'utiliser boost :: noncopyable.

18
fmuecke

Les constructeurs ajoutent une conversion implicite. Pour supprimer cette conversion implicite, il est nécessaire de déclarer un constructeur avec un paramètre explicite.

En C++ 11, vous pouvez également spécifier un "type d'opérateur ()" avec un tel mot clé http://en.cppreference.com/w/cpp/language/explicit Avec une telle spécification, vous pouvez utiliser un opérateur en termes de conversions explicites et d'initialisation directe d'objet.

P.S. Lors de l'utilisation de transformations définies par l'utilisateur (via les constructeurs et l'opérateur de conversion de type), un seul niveau de conversion implicite est autorisé. Mais vous pouvez combiner ces conversions avec des conversions dans d'autres langues.

  • up rangs intégraux (char à int, float à doubler);
  • conversions standart (int à doubler);
  • convertir les pointeurs d'objets en classe de base et en void *;
5
bruziuz