Partager via


Fonctions utilisées par défaut et supprimées explicitement

En C++11, les fonctions utilisées par défaut et supprimées vous permettent de contrôler si les fonctions membres spéciales sont générées automatiquement. Les fonctions supprimées vous offrent également un langage simple pour empêcher la promotions de types problématiques dans les arguments de fonctions de tous types - les fonctions membres de type spéciaux, ainsi que les fonctions non-membres - qui provoqueraient sinon un appel de fonction indésirable.

Avantages des fonctions supprimées et explicitement définies par défaut

Dans C++, le compilateur génère automatiquement le constructeur par défaut, le constructeur de copie, l'opérateur d'assignation de copie et le destructeur d'un type s'il ne déclare pas les siens. Ces fonctions sont connues sous le nom de fonctions membres spéciales, et elles permettent à des types définis par l'utilisateur dans C++ de se comporter comme des structures dans C. Autrement dit, vous pouvez les créer, les copier et les détruire sans effort de codage supplémentaire. C++11 apporte la sémantique de déplacement au langage et ajoute le constructeur de déplacement et l'opérateur d'assignation de mouvement à la liste des fonctions membres spéciales que le compilateur peut générer automatiquement.

Cela est pratique pour les types simples, mais les types complexes définissent eux-mêmes souvent une ou plusieurs des fonctions membres spéciales, et cela peut empêcher d'autres fonctions membres spéciales d'être générées automatiquement. Dans la pratique :

  • Si un constructeur est déclaré explicitement, aucun constructeur par défaut n'est automatiquement généré.

  • Si un destructeur virtuel est déclaré explicitement, aucun destructeur par défaut n'est automatiquement généré.

  • Si un constructeur de déplacement ou un opérateur d'assignation de déplacement est déclaré explicitement :

    • Aucun constructeur de copie n'est généré automatiquement.

    • Aucun opérateur d'assignation de copie n'est généré automatiquement.

  • Si un constructeur de copie, un opérateur d'assignation de copie, un constructeur de déplacement, un opérateur de mouvement- assignation, ou un destructeur est déclaré explicitement, alors :

    • Aucun constructeur de déplacement n'est généré automatiquement.

    • Aucun opérateur d'assignation de déplacement n'est généré automatiquement.

Notes

En outre, Standard C++11 spécifie les règles supplémentaires suivantes :

  • Si un constructeur ou destructeur de copie est déclaré explicitement, la génération automatique de l'opérateur d'assignation de copie est déconseillée.

  • Si un constructeur ou destructeur de copie est déclaré explicitement, la génération automatique du constructeur de copie est déconseillée.

Dans les deux cas, Visual Studio continue à générer automatiquement des fonctions nécessaires implicitement, et n'émet pas d'avertissement.

Les conséquences de ces règles peuvent également avoir des conséquences dans la hiérarchie d'objets. Par exemple, si pour une raison quelconque, une classe de base n'a pas de constructeur par défaut qui peut être appelé d'une classe dérivée, c'est-à-dire un constructeur public ou protected qui ne prend pas de paramètre - alors une classe qui dérive de celle-ci ne peut pas générer automatiquement son propre constructeur par défaut.

Ces règles peuvent compliquer l'implémentation d'idiomes C++ courant et de types définis par l'utilisateur qui devrait pourtant s'avérer simple (par exemple, rendre un type défini par l'utilisateur impossible à copier en déclarant le constructeur de copie et l'opérateur d'assignation de copie en privé et en ne les définissant pas).

struct noncopyable
{
  noncopyable() {};
  
private:
  noncopyable(const noncopyable&);
  noncopyable& operator=(const noncopyable&);
};

Avant C++11, cet extrait de code était la forme idiomatique des types non-copiables. Toutefois, il présente plusieurs problèmes :

  • Le constructeur de copie doit être déclaré de manière privée pour le masquer, mais comme il n'est pas déclaré du tout, la génération automatique du constructeur par défaut est bloquée. Vous devez définir explicitement le constructeur par défaut si vous en souhaitez un, même s'il ne fait rien.

  • Même si le constructeur explicitement défini par défaut ne fait rien, il est considéré comme étant non trivial par le compilateur. Il est moins efficace que le constructeur par défaut généré automatiquement et empêche un type noncopyable d'être un type POD réel.

  • Bien que le constructeur de copie et l'opérateur d'assignation de copie soient masqués vis-à-vis du code externe, les fonctions membres et les amis du noncopyable peuvent tout de même être vus et appelés. S'ils sont déclarés mais non définis, leur appel entraîne une erreur de l'éditeur de liens.

  • Bien qu'il s'agisse d'un idiome couramment accepté, l'intention n'est pas claire tant que vous ne comprenez pas toutes les règles de génération automatique des fonctions membres spéciales.

En C++11, l'idiome non copiable peut être implémenté de façon plus simple.

struct noncopyable
{
  noncopyable() =default;
  noncopyable(const noncopyable&) =delete;
  noncopyable& operator=(const noncopyable&) =delete;
};

Notez comment les problèmes d'idiome pre-C++11 sont résolus :

  • La génération du constructeur par défaut est toujours empêchée en déclarant un autre constructeur de copie, mais réactivez la en la définissant comme valeur par défaut explicite.

  • Les fonctions membres spéciales utilisées par défaut explicitement sont toujours considérées comme triviales, par conséquent, il n'y a aucune altération des performances et un type noncopyable peut être un type POD réel.

  • Le constructeur de copie et l'opérateur d'assignation de copie sont publics mais supprimés. Définir ou appeler une fonction supprimée constitue une erreur de compilation.

  • L'objectif est clair pour toute personne qui comprend =default et =delete. Vous ne devez pas inclure les règles pour la génération automatique des fonctions membres spéciales.

Des idiomes similaires existent pour créer des types définis par l'utilisateur qui ne peuvent pas être déplacés, qui ne peuvent qu'être alloués dynamiquement ou qui ne peuvent pas être alloués dynamiquement. Chacun de ces idiomes possède des implémentations pré-C++11 qui connaissent des problèmes similaires, et qui sont résolus dans C++11 par leur implémentation en termes de fonctions membres spéciales utilisées par défaut et supprimées.

Fonctions utilisées par défaut explicitement

Vous pouvez définir par défaut n'importe laquelle des fonctions membres spéciales – pour explicitement établir que la fonction membre spéciale utilise l'implémentation par défaut, pour définir la fonction membre spéciale avec un qualificateur d'accès non public ou pour rétablir une fonction membre spéciale dont la génération automatique a été empêchée par d'autres circonstances.

Vous définissez par défaut une fonction membre spéciale en le déclarant comme dans cet exemple :

struct widget
{
  widget()=default;

  inline widget& operator=(const widget&);
};

inline widget& widget::operator=(const widget&) =default;

Notez que vous pouvez utiliser par défaut une fonction membre spéciale en dehors du corps d'une classe tant qu'elle peut être inline.

Étant donné les avantages des fonctions membres spéciales ordinaires en termes de performance, nous vous recommandons de préférer les fonctions membres spéciales générées automatiquement aux corps de fonction vides lorsque vous souhaitez rétablir le comportement par défaut. Vous pouvez effectuer cette opération en définissant explicitement par défaut la fonction membre spéciale, ou en ne le déclarant pas (et en ne déclarant pas non plus d'autres fonctions membres spéciales qui l'empêcheraient d'être générée automatiquement.)

Notes

Visual Studio ne prend pas en charge les constructeurs de déplacement ou les opérateurs d'assignation de mouvement comme la norme C++11 spécifie.Pour plus d'informations, consultez la section Fonctions utilisées par défaut et supprimées de Prise en charge des fonctionnalités C++11 (Modern C++).

Fonctions supprimées

Vous pouvez supprimer des fonctions membres spéciales ainsi que des fonctions membres normales et des fonctions non-membres pour empêcher qu'elles ne soient définies ou appelées. Supprimer des fonctions membres spéciales offre un moyen plus net d'empêcher le compilateur de générer des fonctions membres spéciales que vous ne souhaitez pas. La fonction doit être supprimée lorsqu'elle est déclarée ; elle ne peut pas être supprimée après, comme une fonction qui est déclarée puis ensuite mise en défaut.

struct widget
{
  // deleted operator new prevents widget from being dynamically allocated.
  void* operator new(std::size_t) =delete;
};

Supprimer les fonctions membres ou non-membres normales empêche les promotions de type problématiques de provoquer l'appel d'une fonction non voulue. Cela fonctionne parce que les fonctions supprimées participent à la résolution de surcharge et fournissent une meilleure correspondance que la fonction qui peut être appelée une fois que les types sont promus. L'appel de fonction résout la fonction la plus spécifique - mais supprimée - et provoque une erreur du compilateur.

// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }

Notez dans l'exemple précédent que l'appel à call_with_true_double_only en utilisant un argument float provoque une erreur du compilateur, mais l'appel à call_with_true_double_only en utilisant un argument int ne le fait pas ; dans le cas de int, l'argument est sera promu de int à double et appellera avec succès la version double de la fonction, même si ce n'est pas forcément ce qui était attendu. Pour garantir que tout appel de cette fonction en utilisant un argument non double provoque une erreur du compilateur, déclarez une version de modèle de la fonction qui est supprimée.

template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.

void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.