web-dev-qa-db-fra.com

Qu'est-ce qu'un constructeur de conversion en C++? Pourquoi est-ce?

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 ;
}
38
octoback

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-specifierexplicit 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-specifierexplicit 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.

47
Joseph Mansfield

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.

13
octoback

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.

1
Fazail awan