Mot clé explicit

Étonnamment, l'initialisation est un sujet complexe en C++. Ce qui suit couvre quelques aspects de cette question, mais vous voudrez sans doute en savoir aussi sur :

Depuis C++ 98 (au moins!), il est possible de qualifier un constructeur du mot clé explicit, pour forcer le code client à affirmer son intention et éviter certains accidents. Par exemple :

Sans constructeur qualifié explicit Avec constructeur qualifié explicit
struct Point {
   float x{}, y{};
   constexpr Point(float x, float y) : x{y}, y{y} {
   }
   Point() = default;
};
class Cercle {
   float rayon{ 1.0f };
   Point centre;
public:
   Cercle() =  default;
   constexpr Cercle(float rayon)
      : rayon{ rayon } {
   }
   // ...
};
void f(Cercle);
int main() {
   // oups! Ceci crée un Cercle{3.5f,Point{}}... Était-ce l'intention?
   f(3.5f);
   f(Cercle{3.5f}); // Crée un Cercle{3.5f,Point{}}
}
struct Point {
   float x{}, y{};
   constexpr Point(float x, float y) : x{y}, y{y} {
   }
   Point() = default;
};
class Cercle {
   float rayon{ 1.0f };
   Point centre;
public:
   Cercle() =  default;
   constexpr explicit Cercle(float rayon)
      : rayon{ rayon } {
   }
   // ...
};
void f(Cercle);
int main() {
   // illégal, car entraînerait la construction implicite d'un Cercle{3.5f,Point{}}
   f(3.5f);
   f(Cercle{3.5f}); // Crée un Cercle{3.5f,Point{}}
}

Les constructeurs explicit sont surtout utiles pour les objets qui peuvent être instanciés à l'aide d'un seul paramètre. Pour quelques exemples :

#include <string>
struct X {
   X(int); // envisagez explicit ici
};
struct Y {
   Y(int, std::string = ""); // envisagez explicit ici
};
struct Z {
   Z(int = 3); // envisagez explicit ici
};

Qualification explicit conditionnelle

Avec C++ 20, il est possible d'apposer explicit de manière conditionnelle.

Empruntant un exemple à Simon Brand, supposons une classe générique wrapper<T> contenant un T et encadrant son utilisation, et supposons que nous souhaitions un constructeur paramétrique capable de construire un T à partir d'un U. Dans l'extrait de code ci-dessous, le constructeur de wrapper<T> acceptant un const U& sera qualifié explicit seulement s'il n'y a pas de conversion implicite d'un T à un U (donc, si la conversion d'un U à un T est implicite, alors la conversion d'un U à un wrapper<T> le sera aussi) :

#include <type_traits>
template <class T>
   struct wrapper { 
      template <class U>
         explicit(!std::is_convertible_v<U, T>)
            wrapper(U const & u) : val(u) {
            } 
      T val;
      // ...
   };

Ceci donne un contrôle plus fin sur ce subtil mais utile mécanisme.

Opérateurs de conversion explicit

Il est possible depuis C++ 11 de qualifier les opérateurs de conversion du mot-clé explicit, comme on pouvait déjà le faire pour les constructeurs paramétriques. Ceci donne ici encore un contrôle plus fin sur ce mécanisme.

Par exemple, avant C++ 11, le code suivant aurait mené à une ambiguïté :

struct Trois {
   operator int() const {
      return 3;
   }
   operator const char*() const {
      return "trois";
   }
};
void f(const char*) {
}
void f(int) {
}
int main() {
   f(Trois{}); // ambigu
}

La raison de cette ambiguïté est qu'il n'existe pas de fonction f(Trois), mais qu'il existe deux fonctions f(int) et f(const char*), or la conversion d'un Trois à un int ou à un const char* est implicite, ce qui empêche le compilateur de faire un choix.

Depuis C++ 11, il est possible de qualifier explicit l'un, l'autre ou tous les opérateurs de conversion, ce qui impose au code client d'expliciter son intention. Nous en arrivons donc à :

struct Trois {
   explicit operator int() const { // <-- ICI
      return 3;
   }
   operator const char*() const {
      return "trois";
   }
};
void f(const char*) {
}
void f(int) {
}
int main() {
   Trois tr;
   // Ok, f(int), conversion explicite
   f(static_cast<int>(tr));
   // Ok, f(int), conversion explicite
   f(int{ tr });
   f(tr); // Ok, f(const char*)
}

... qui compile sans ambiguïté.

Lectures complémentaires

Quelques liens pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !