Question

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

Était-ce utile?

La solution

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

Vous voudrez peut-être procéder ainsi afin d'éviter toute construction accidentelle susceptible de masquer 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 & amp;) et vous appelez print (3) (lorsque vous réellement appelez ("3") ). Vous vous attendez à ce qu'il imprime "3", mais une chaîne vide de longueur 3 est imprimée à la place.

Autres conseils

Supposons que vous avez 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
};

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 Chaîne (const char * s) , c'est probablement exactement ce que vous voulez. Vous pouvez passer un const char * à une fonction qui attend une String et le compilateur construira automatiquement un objet temporaire String pour vous.

D'autre part, si vous avez une classe de mémoire tampon dont le constructeur Buffer (taille int.) prend la taille de la mémoire tampon en octets, vous ne voulez probablement pas que le compilateur tourne silencieusement int dans Buffer . 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 souhaitez transmettre 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 comme explicit pour éviter que le compilateur vous surprenne avec des conversions inattendues.

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 la 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 prêter à confusion ou ne pas être conforme à l’intention du programmeur. Le préfixe du mot clé explicit au constructeur générerait une erreur de compilation à Foo bar2 = 20; .

Il est généralement conseillé de déclarer les constructeurs à argument unique comme explicite , à 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

peut être utilisé en tant que constructeur à un seul argument. Donc, vous voudrez peut-être aussi rendre explicite .

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

Ici est une bonne écriture- sur les constructeurs explicites.

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

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 uniquement considérée 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 une initialisation directe (8.5). Sinon, les conversions définies par l'utilisateur ne sont pas limitées à une utilisation dans les affectations.   et initialisations.

Présentation

Les fonctions de conversion explicites et les constructeurs 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 une petite configuration de structures et de fonctions pour voir la différence entre les conversions explicites et non explicites .

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

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 pouvant être implicitement construite à partir 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 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 doit utiliser le constructeur de U ou la fonction de conversion pour convertir l'objet V en un type à passer à f .

Si le constructeur de U ou la fonction de conversion de V serait explicite , il n'y aurait aucune ambiguïté puisque seul le non une conversion explicite serait envisagée. Si les deux sont explicites, l'appel à f à l'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 de manière implicite un numéro simple:

  

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

Constructeurs de conversion explicite (C ++ uniquement)

  

Le spécificateur de fonction explicite contrôle le type implicite indésirable   conversions. Il ne peut être utilisé que dans les déclarations de constructeurs   dans une déclaration de classe. Par exemple, sauf pour la valeur par défaut   constructeur, les constructeurs de la classe suivante sont des conversions   constructeurs.

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 équivaut à A c = A (1); .

Si vous déclarez le constructeur de la classe comme explicite , 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 pouvez uniquement affecter 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);

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 placé 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 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, le mot-clé explicit impose uniquement la validation des conversions explicites. Par conséquent, bool b = c; serait une distribution non valide dans ce cas. Dans des situations comme celle-ci, explicit -keyword peut aider le programmeur à éviter les conversions implicites et non intentionnelles. Cet usage a été normalisé dans C ++ 11 .

Cpp Reference est toujours utile !!! Vous trouverez des détails sur le spécificateur explicite ici . Vous devrez peut-être consulter les conversions implicites et initialisation de la copie également.

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
}

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, 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 voudrez bien le faire ...

Une autre bonne pratique pour les classes consiste à rendre la construction de la copie et l’affectation privées (par exemple, à les désactiver), sauf si vous avez vraiment besoin de les 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érivée de boost :: noncopyable.

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 " opérateur type () " avec ce mot clé http://fr.cppreference.com/w/cpp/language/explicit Avec une telle spécification, vous pouvez utiliser l'opérateur en termes de conversions explicites et d'initialisation directe d'un 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 *;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top