Remarque
L’accès à cette page requiert une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page requiert une autorisation. Vous pouvez essayer de modifier des répertoires.
En C++11, les fonctions utilisées par défaut et supprimées vous permettent de contrôler de façon explicite si les fonctions membres spéciales sont générées automatiquement. Les fonctions supprimées vous donnent également un langage simple pour empêcher les promotions de type problématiques de se produire dans des arguments vers des fonctions de tous les types ( fonctions membres spéciales, fonctions membres normales et fonctions membres non membres) qui entraîneraient autrement un appel de fonction indésirable.
Avantages des fonctions utilisées par défaut et supprimées explicitement
En 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 son propre constructeur. Ces fonctions sont appelées fonctions membres spéciales, ce qui rend les types simples définis par l’utilisateur dans C++ se comportent comme des structures en C. Autrement dit, vous pouvez créer, copier et les détruire sans effort de codage supplémentaire. C++11 fournit 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 d'assignation de mouvement ou un destructeur est déclaré explicitement :
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.
Remarque
En outre, la norme C++11 spécifie les règles supplémentaires suivantes :
- Si un constructeur de copie ou un destructeur est déclaré explicitement, la génération automatique de l'opérateur d'assignation de copie est déconseillée.
- Si un opérateur d'assignation de copie ou un destructeur est déclaré explicitement, la génération automatique du constructeur de copie est déconseillée.
Dans les deux cas, Visual Studio continue de générer automatiquement les fonctions nécessaires implicitement et n’émet pas d’avertissement par défaut. Dans la mesure où Visual Studio 2022 version 17.7, C5267 peut être activé pour émettre un avertissement.
Les conséquences de ces règles peuvent également se répercuter dans la hiérarchie des objets. Par exemple, si, pour une raison quelconque, une classe de base ne parvient pas à avoir un constructeur par défaut pouvant être appelé à partir d’une classe dérivée ( autrement dit, un ou public un protected constructeur qui ne prend aucun 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 de ce qui doit être des types directement avancés, définis par l’utilisateur et des idiomes C++ courants, par exemple, ce qui rend un type défini par l’utilisateur noncopyable en déclarant le constructeur de copie et l’opérateur d’assignation de copie en privé et 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 noncopyables. Toutefois, il présente plusieurs problèmes :
Le constructeur de copie doit être déclaré en privé pour le masquer, mais parce qu’il est déclaré du tout, la génération automatique du constructeur par défaut est empêché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 par défaut explicitement défini ne fait rien, le compilateur considère qu’il est nontrivial. Il est moins efficace qu'un constructeur par défaut généré automatiquement et empêche un type
noncopyabled'ê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 de
noncopyablepeuvent tout de même les voir et les appeler. S’ils sont déclarés mais non définis, ils provoquent une erreur de l’éditeur de liens.Bien qu’il s’agisse d’un idiome couramment accepté, l’intention n’est pas claire, sauf si vous comprenez toutes les règles de génération automatique des fonctions membres spéciales.
En C++11, l’idiome noncopyable peut être implémenté de manière plus simple.
struct noncopyable
{
noncopyable() =default;
noncopyable(const noncopyable&) =delete;
noncopyable& operator=(const noncopyable&) =delete;
};
Notez comment les problèmes d'idiome pré-C++11 sont résolus :
La génération du constructeur par défaut est toujours empêchée en déclarant le constructeur de copie, mais vous pouvez la réactiver en la définissant comme valeur par défaut explicite.
Les fonctions membres spéciales explicitement par défaut sont toujours considérées comme triviales, il n’y a donc aucune pénalité de performances et
noncopyablen’est pas empêchée d’être un vrai type POD.Le constructeur de copie et l'opérateur d'assignation de copie sont publics, mais supprimés. Il s’agit d’une erreur au moment de la compilation pour définir ou appeler une fonction supprimée.
L'intention est claire pour toute personne qui comprend
=defaultet=delete. Vous ne devez pas comprendre les règles pour la génération automatique des fonctions membres spéciales.
Des idiomes similaires existent pour rendre des types définis par l’utilisateur qui ne sont pas modifiables, qui peuvent uniquement ê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 de façon identique en C++11 en les implémentant en termes de fonctions membres spéciales utilisées par défaut et supprimées.
Fonctions utilisées par défaut explicitement
Vous pouvez par défaut n’importe quelle fonction membre spéciale , pour indiquer explicitement 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 la 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 par défaut utiliser une fonction membre spéciale en dehors du corps d’une classe tant qu’elle est inlinable.
É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 la 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.)
Fonctions supprimées
Vous pouvez supprimer des fonctions membres spéciales et des fonctions membres normales et des fonctions non membres pour les empêcher d’être définies ou appelées. La suppression de fonctions membres spéciales permet 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 à mesure qu’elle est déclarée ; il ne peut pas être supprimé par la suite de la façon dont une fonction peut être déclarée, puis ultérieurement par défaut.
struct widget
{
// deleted operator new prevents widget from being dynamically allocated.
void* operator new(std::size_t) = delete;
};
La suppression de la fonction membre normale ou des fonctions non membres empêche les promotions de type problématiques d’appeler une fonction inattendue. Cela fonctionne, car les fonctions supprimées participent toujours à 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 que dans l’exemple précédent, l’appel call_with_true_double_only à l’aide d’un float argument provoquerait une erreur du compilateur, mais l’appel call_with_true_double_only à l’aide d’un int argument ne le ferait pas ; dans le int cas, l’argument sera promu int et double appellera avec succès la double version de la fonction, même si ce n’est peut-être pas ce que vous envisagez. Pour vous assurer que tout appel à cette fonction à l’aide d’un argument non double provoque une erreur du compilateur, vous pouvez déclarer une version de modèle de la fonction 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.