c++ wikiversity Que signifie le mot clé explicite?




valeur unsigned int (9)

Cpp Reference est toujours utile !!! Des détails sur le spécificateur explicite peuvent être trouvés here . Vous devrez peut-être également examiner les conversions implicites et l’ copy-initialization .

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
}

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


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 étant explicit :

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

Le mot clé explicit accompagne soit

  • un 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 exactement comme des constructeurs non explicites, mais uniquement 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 prise en compte 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 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 et les fonctions de conversion non explicites 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 une petite configuration de structures et de fonctions pour voir la différence entre explicit conversions explicit et 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 d'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 d'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 explicit ou des constructeurs?

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 s’il passe un objet de type V

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

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

Si le constructeur de U ou la fonction de conversion de V serait explicit , il n'y aurait aucune ambiguïté puisque seule la conversion non explicite serait prise en compte. Si les deux sont explicites, l'appel de f aide d'un objet de type V doit ê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 entraîner 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 Bjarne Stroustrup le dit (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.


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.

De plus, 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 est de rendre la construction de la copie et l’affectation privées (autrement dit de les désactiver), sauf si vous avez vraiment besoin de les implémenter. Cela évite d'avoir des copies éventuelles de pointeurs lorsque vous utilisez les méthodes que C ++ créera pour vous par défaut. Une autre façon de faire est d'utiliser boost :: noncopyable.


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 correspond pas à 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 recommandé de déclarer explicit constructeurs à un seul argument, à moins que votre implémentation ne l’interdit expressément.

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 les explicit .

Par exemple, si vous souhaitez délibérément rendre votre constructeur à argument unique explicite, créez un foncteur (regardez la structure 'add_x' déclarée dans this réponse). Dans un tel cas, créer un objet comme add_x add30 = 30; aurait probablement un sens.

Here un bon article sur les constructeurs explicites.


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


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 seul paramètre 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 lors de l'appel de la fonction DoBar (42) . Il faut maintenant appeler explicitement pour la conversion avec DoBar (Foo (42))

La raison pour laquelle vous voudrez peut-être faire cela est d’éviter toute construction accidentelle pouvant cacher des bugs. Exemple élaboré:

  • Vous avez une 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) (alors que vous aviez l'intention d'appeler print("3") ). Vous vous attendez à ce qu'il imprime "3", mais une chaîne vide de longueur 3 est imprimée à la place.

Constructeurs de conversion explicites (C ++ uniquement)

Le spécificateur de fonction explicite contrôle les conversions de type 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 comme 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);

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 transmettre un caractère const char* à une fonction qui attend une String et le compilateur construira automatiquement un objet String temporaire pour vous.

D'autre part, si vous avez une classe de mémoire tampon dont le constructeur Buffer(int size) prend la taille de la mémoire tampon en octets, vous ne voudrez probablement pas que le compilateur transforme silencieusement int s en Buffer . Pour empêcher 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 Buffer temporaire, 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 explicit pour empêcher le compilateur de vous surprendre avec des conversions inattendues.





explicit-constructor