J'ai entendu dire que C++ avait quelque chose appelé "constructeurs de conversion" ou "constructeurs de conversion". Que sont-ils et à quoi servent-ils? Je l'ai vu mentionné en ce qui concerne ce code:
class MyClass
{
public:
int a, b;
MyClass( int i ) {}
}
int main()
{
MyClass M = 1 ;
}
La définition d'un constructeur converting est différente entre C++ 03 et C++ 11. Dans les deux cas, il doit s'agir d'un constructeur non -explicit
(sinon, il ne serait pas impliqué dans les conversions implicites), mais pour C++ 03, il doit également être appelable avec un seul argument. C'est:
struct foo
{
foo(int x); // 1
foo(char* s, int x = 0); // 2
foo(float f, int x); // 3
explicit foo(char x); // 4
};
Les constructeurs 1 et 2 convertissent tous les deux des constructeurs en C++ 03 et C++ 11. Le constructeur 3, qui doit prendre deux arguments, n'est qu'un constructeur de conversion en C++ 11. Le dernier, constructeur 4, n'est pas un constructeur de conversion car il s'agit de explicit
.
C++ 03 : §12.3.1
Un constructeur déclaré sans le function-specifier
explicit
qui peut être appelé avec un seul paramètre spécifie une conversion du type de son premier paramètre au type de sa classe. Un tel constructeur s'appelle un constructeur de conversion.
C++ 11 : §12.3.1
Un constructeur déclaré sans le function-specifier
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.
Pourquoi les constructeurs avec plus d'un paramètre sont-ils considérés comme convertissant des constructeurs en C++ 11? En effet, la nouvelle norme nous fournit une syntaxe pratique pour passer des arguments et renvoyer des valeurs à l'aide de braced-init-lists. Prenons l'exemple suivant:
foo bar(foo f)
{
return {1.0f, 5};
}
La possibilité de spécifier la valeur de retour sous la forme braced-init-list est considérée comme une conversion. Cela utilise le constructeur de conversion pour foo
qui prend un float
et un int
. De plus, nous pouvons appeler cette fonction en faisant bar({2.5f, 10})
. C'est aussi une conversion. S'agissant de conversions, il est logique que les constructeurs qu'ils utilisent soient converting constructors.
Il est donc important de noter, par conséquent, que le constructeur de foo
qui prend un float
et un int
avoir le spécificateur de fonction explicit
arrêterait la compilation du code ci-dessus. La nouvelle syntaxe ci-dessus ne peut être utilisée que si un constructeur de conversion est disponible pour effectuer le travail.
C++ 11 : §6.6.3:
Une instruction
return
avec un braced-init-list initialise l'objet ou la référence à renvoyer à partir de la fonction par copy-list-initialization (8.5.4) à partir de la liste d'initialiseurs spécifiée.
§ 8.5:
L'initialisation qui se produit [...] lors de la transmission d'argument [...] est appelée initialisation par copie.
§12.3.1:
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 explicitement utilisés.
Conversion implicite avec conversion constructeur
Rendons l'exemple de la question plus complexe
class MyClass
{
public:
int a, b;
MyClass( int i ) {}
MyClass( const char* n, int k = 0 ) {}
MyClass( MyClass& obj ) {}
}
Les deux premiers constructeurs convertissent des constructeurs. Le troisième est un constructeur de copie et, en tant que tel, un autre constructeur de conversion.
Un constructeur de conversion permet la conversion implicite d'un type d'argument vers un type de constructeur. Ici, le premier constructeur permet la conversion d'un int
en un objet de classe MyClass
. Le second constructeur permet la conversion d'une chaîne en un objet de classe MyClass
. Et troisièmement ... d'un objet de classe MyClass
à un objet de classe MyClass
!
Pour être un constructeur de conversion, le constructeur doit avoir un seul argument (le second argument a une valeur par défaut) et être déclaré sans le mot clé explicit
.
Ensuite, l’initialisation dans main peut ressembler à ceci:
int main()
{
MyClass M = 1 ;
// which is an alternative to
MyClass M = MyClass(1) ;
MyClass M = "super" ;
// which is an alternative to
MyClass M = MyClass("super", 0) ;
// or
MyClass M = MyClass("super") ;
}
Mots clés et constructeurs explicites
Maintenant, si nous avions utilisé le mot clé explicit
?
class MyClass
{
public:
int a, b;
explicit MyClass( int i ) {}
}
Ensuite, le compilateur n'accepterait pas
int main()
{
MyClass M = 1 ;
}
puisqu'il s'agit d'une conversion implicite. Au lieu de cela, écrire
int main()
{
MyClass M(1) ;
MyClass M = MyClass(1) ;
MyClass* M = new MyClass(1) ;
MyClass M = (MyClass)1;
MyClass M = static_cast<MyClass>(1);
}
explicit
mot-clé doit toujours être utilisé pour empêcher la conversion implicite pour un constructeur et s'applique au constructeur dans une déclaration de classe.
Un constructeur de conversion est un constructeur à paramètre unique déclaré sans le spécificateur de fonction explicit. Le compilateur utilise des constructeurs de conversion pour convertir des objets du type du premier paramètre au type de la classe du constructeur de conversion.