Que signifie le mot clé explicit
en C++?
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é:
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.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
};
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 int
s en Buffer
s. 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.
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
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.
Le mot clé explicit
transforme un constructeur de conversion en constructeur de non-conversion. En conséquence, le code est moins sujet aux erreurs.
explicit
accompagne soitC++ [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).
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.
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
*/
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) { }
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
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
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.)
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.
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);
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 .
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
}
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.
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.