Signification de = supprimer après la déclaration de fonction

242
class my_class
{
    ...
    my_class(my_class const &) = delete;
    ...
};

Que = deletesignifie dans ce contexte?

Y a-t-il d'autres "modificateurs" (autres que = 0et = delete)?

Pat O'Keefe
la source
23
@Blindy Il sera standard en C ++ 0x, c'est à dire bientôt.
Konrad Rudolph
1
Je me tiens corrigé, j'avais manqué cette fonctionnalité C ++ 0x. Je pensais que c'était un #definea la Qt évalué à 0 puis déclaré une fonction cachée ou quelque chose.
Blindy
J'ai un souvenir d'un mot-clé «désactiver» qui signifie le même ou quelque chose de similaire. Suis-je en train de l'imaginer? Ou y a-t-il une différence subtile entre eux?
Stewart

Réponses:

201

La suppression d'une fonction est une fonctionnalité C ++ 11 :

L'idiome commun d '"interdiction de copier" peut désormais s'exprimer directement:

class X {
    // ...
    X& operator=(const X&) = delete;  // Disallow copying
    X(const X&) = delete;
};

[...]

Le mécanisme de "suppression" peut être utilisé pour n'importe quelle fonction. Par exemple, nous pouvons éliminer une conversion indésirable comme celle-ci:

struct Z {
    // ...

    Z(long long);     // can initialize with an long long         
    Z(long) = delete; // but not anything less
};
Prasoon Saurav
la source
3
La méthode traditionnelle pour "interdire la copie" n'est-elle pas juste de rendre le copy-ctor et operator = "privé"? Cela va un peu plus loin et demande au compilateur de ne même pas générer les fonctions. S'ils sont à la fois privés et = supprimés, la copie est-elle doublement interdite?
Reb.Cabin
8
@Reb, =deleterend la méthode inaccessible même à partir de contextes qui peuvent voir les privateméthodes (c'est-à-dire au sein de la classe et de ses amis). Cela supprime toute incertitude lorsque vous lisez le code. @Prasoon, ce deuxième exemple supprime toujours les constructeurs - ce serait bien de voir un supprimé operator long ()par exemple.
Toby Speight
2
@ Reb.Cabin Using = deleteest préférable à l'utilisation privateou à d'autres mécanismes similaires, car vous voulez généralement que la fonction interdite soit déclarée visiblement et prise en compte pour la résolution de surcharge, etc., afin qu'elle puisse échouer le plus tôt possible et fournir l'erreur la plus claire à l'utilisateur. Toute solution qui consiste à «masquer» la déclaration réduit cet effet.
Leushenko
1
Existe-t-il une raison particulière pour rendre le constructeur de copie public et appliquer le mot clé delete? Pourquoi ne pas laisser le constructeur privé et appliquer le mot-clé?
Dohn Joe
81
  1. = 0signifie qu'une fonction est pure virtuelle et que vous ne pouvez pas instancier un objet de cette classe. Vous devez en dériver et implémenter cette méthode
  2. = deletesignifie que le compilateur ne générera pas ces constructeurs pour vous. AFAIK ceci n'est autorisé que sur le constructeur de copie et l'opérateur d'affectation. Mais je ne suis pas trop bon pour la prochaine norme.
mkaes
la source
4
Il existe d'autres utilisations de la =deletesyntaxe. Par exemple, vous pouvez l'utiliser pour interdire explicitement certains types de conversions implicites qui pourraient avoir lieu avec l'appel. Pour cela, vous supprimez simplement les fonctions surchargées. Jetez un œil à la page Wikipedia sur C ++ 0x pour plus d'informations.
LiKao
Je le ferai dès que j'en trouverai. Devinez qu'il est temps de rattraper c ++ 0X
mkaes
Oui, C ++ 0x est génial. Je ne peux pas attendre que GCC 4.5+ soit plus courant, donc je peux commencer à utiliser des lambdas.
LiKao
5
La description de = deleten'est pas entièrement correcte. = deletepeut être utilisé pour n'importe quelle fonction, auquel cas il est explicitement marqué comme supprimé et toute utilisation entraîne une erreur de compilation. Pour les fonctions membres spéciales, cela signifie également en particulier qu'elles ne sont alors pas générées pour vous par le compilateur, mais ce n'est que le résultat de leur suppression, et non ce qui = deleteest vraiment.
MicroVirus
28

Cet extrait du langage de programmation C ++ [4e édition] - Le livre de Bjarne Stroustrup parle du véritable objectif de l'utilisation =delete:

3.3.4 Suppression des opérations

L'utilisation de la copie ou du déplacement par défaut pour une classe dans une hiérarchie est généralement un désastre : étant donné uniquement un pointeur vers une base, nous ne savons tout simplement pas quels membres la classe dérivée possède, nous ne pouvons donc pas savoir comment les copier . Ainsi, la meilleure chose à faire est généralement de supprimer les opérations de copie et de déplacement par défaut, c'est-à-dire d'éliminer les définitions par défaut de ces deux opérations:

class Shape {
public:
  Shape(const Shape&) =delete; // no copy operations
  Shape& operator=(const Shape&) =delete;

  Shape(Shape&&) =delete; // no move operations
  Shape& operator=(Shape&&) =delete;
  ˜Shape();
    // ...
};

Maintenant, une tentative de copie d'une forme sera interceptée par le compilateur.

Le =deletemécanisme est général, c'est-à-dire qu'il peut être utilisé pour supprimer toute opération

Saurav Sahu
la source
5

Les normes de codage avec lesquelles j'ai travaillé ont eu les éléments suivants pour la plupart des déclarations de classe.

//  coding standard: disallow when not used
T(void)                  = delete; // default ctor    (1)
~T(void)                 = delete; // default dtor    (2)
T(const T&)              = delete; // copy ctor       (3)
T(const T&&)             = delete; // move ctor       (4)
T& operator= (const T&)  = delete; // copy assignment (5)
T& operator= (const T&&) = delete; // move assignment (6)

Si vous utilisez l'un de ces 6, il vous suffit de commenter la ligne correspondante.

Exemple: la classe FizzBus ne nécessite que dtor, et donc n'utilise pas les 5 autres.

//  coding standard: disallow when not used
FizzBuzz(void)                         = delete; // default ctor (1)
// ~FizzBuzz(void);                              // dtor         (2)
FizzBuzz(const FizzBuzz&)              = delete; // copy ctor    (3)
FizzBuzz& operator= (const FizzBuzz&)  = delete; // copy assig   (4)
FizzBuzz(const FizzBuzz&&)             = delete; // move ctor    (5)
FizzBuzz& operator= (const FizzBuzz&&) = delete; // move assign  (6)

Nous commentons seulement 1 ici et installons l'implémentation de celui-ci ailleurs (probablement là où la norme de codage le suggère). Les 5 autres (sur 6) sont interdits de suppression.

Vous pouvez également utiliser '= supprimer' pour interdire les promotions implicites de valeurs de tailles différentes ... exemple

// disallow implicit promotions 
template <class T> operator T(void)              = delete;
template <class T> Vuint64& operator=  (const T) = delete;
template <class T> Vuint64& operator|= (const T) = delete;
template <class T> Vuint64& operator&= (const T) = delete;
2785528
la source
3

= deleteest une fonctionnalité introduite en C ++ 11. Selon, =deleteil ne sera pas autorisé à appeler cette fonction.

En détail.

Supposons que dans une classe.

Class ABC{
 Int d;
 Public:
  ABC& operator= (const ABC& obj) =delete
  {

  }
};

Lors de l'appel de cette fonction pour l'affectation d'obj, elle ne sera pas autorisée. L'opérateur d'affectation de moyens va restreindre la copie d'un objet à un autre.

ashutosh
la source
2

Nouveau standard C ++ 0x. Veuillez consulter la section 8.4.3 du projet de travail N3242

Dubnde
la source
Whoa, ce projet est bien dépassé. Voici la dernière (en date du 3 avril 2011): open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf
TonyK
Merci et mis à jour le lien. Très utile pour obtenir la version actuelle. La section / le contenu référencé était correct, même dans l'ancien projet, donc je ne comprends pas le vote négatif.
dubnde
1

Une fonction supprimée est implicitement en ligne

(Addendum aux réponses existantes)

... Et une fonction supprimée doit être la première déclaration de la fonction (sauf pour la suppression de spécialisations explicites de modèles de fonction - la suppression doit se faire à la première déclaration de la spécialisation), ce qui signifie que vous ne pouvez pas déclarer une fonction et la supprimer plus tard, par exemple, à sa définition locale à une unité de traduction.

Citant [dcl.fct.def.delete] / 4 :

Une fonction supprimée est implicitement en ligne. ( Remarque: la règle à une définition ( [basic.def.odr] ) s'applique aux définitions supprimées. - note de fin ] Une définition supprimée d'une fonction doit être la première déclaration de la fonction ou, pour une spécialisation explicite d'un modèle de fonction , la première déclaration de cette spécialisation. [Exemple:

struct sometype {
  sometype();
};
sometype::sometype() = delete;      // ill-formed; not first declaration

- fin exemple )

Un modèle de fonction principale avec une définition supprimée peut être spécialisé

Bien qu'une règle générale soit d'éviter la spécialisation des modèles de fonction car les spécialisations ne participent pas à la première étape de la résolution des surcharges, il existe certains contextes où cela peut être utile. Par exemple, lors de l'utilisation d'un modèle de fonction principale non surchargé sans définition pour correspondre à tous les types que l'on ne voudrait pas implicitement convertis en une surcharge par correspondance par conversion; c'est-à-dire pour supprimer implicitement un certain nombre de correspondances de conversion implicite en implémentant uniquement des correspondances de type exact dans la spécialisation explicite du modèle de fonction principale non défini et non surchargé.

Avant le concept de fonction supprimé de C ++ 11, on pouvait le faire en omettant simplement la définition du modèle de fonction principale, mais cela donnait des erreurs de référence indéfinies obscures qui sans doute ne donnaient aucune intention sémantique de l'auteur du modèle de fonction primaire (intentionnellement omis ?). Si au lieu de cela, nous supprimons explicitement le modèle de fonction principale, les messages d'erreur au cas où aucune spécialisation explicite appropriée ne serait trouvée deviennent beaucoup plus agréables et montrent également que l'omission / la suppression de la définition du modèle de fonction principale était intentionnelle.

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t);

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    //use_only_explicit_specializations(str); // undefined reference to `void use_only_explicit_specializations< ...
}

Cependant, au lieu d'omettre simplement une définition du modèle de fonction principal ci-dessus, ce qui génère une erreur de référence obscure non définie lorsqu'aucune spécialisation explicite ne correspond, la définition du modèle principal peut être supprimée:

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t) = delete;

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    use_only_explicit_specializations(str);
    /* error: call to deleted function 'use_only_explicit_specializations' 
       note: candidate function [with T = std::__1::basic_string<char>] has 
       been explicitly deleted
       void use_only_explicit_specializations(T t) = delete; */
}

Donner un message d'erreur plus lisible, où l'intention de suppression est également clairement visible (où une erreur de référence non définie pourrait conduire le développeur à penser que c'est une erreur irréfléchie).

Revenons à pourquoi voudrions-nous jamais utiliser cette technique? Encore une fois, des spécialisations explicites pourraient être utiles pour implicitement supprimer les conversions implicites.

#include <cstdint>
#include <iostream>

void warning_at_best(int8_t num) { 
    std::cout << "I better use -Werror and -pedantic... " << +num << "\n";
}

template< typename T >
void only_for_signed(T t) = delete;

template<>
void only_for_signed<int8_t>(int8_t t) {
    std::cout << "UB safe! 1 byte, " << +t << "\n";
}

template<>
void only_for_signed<int16_t>(int16_t t) {
    std::cout << "UB safe! 2 bytes, " << +t << "\n";
}

int main()
{
    const int8_t a = 42;
    const uint8_t b = 255U;
    const int16_t c = 255;
    const float d = 200.F;

    warning_at_best(a); // 42
    warning_at_best(b); // implementation-defined behaviour, no diagnostic required
    warning_at_best(c); // narrowing, -Wconstant-conversion warning
    warning_at_best(d); // undefined behaviour!

    only_for_signed(a);
    only_for_signed(c);

    //only_for_signed(b);  
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = unsigned char] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */

    //only_for_signed(d);
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = float] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */
}
dfri
la source
0

C'est une nouveauté dans les normes C ++ 0x où vous pouvez supprimer une fonction héritée.

Tayyab
la source
11
Vous pouvez supprimer n'importe quelle fonction. Par exemple, void foo(int); template <class T> void foo(T) = delete;arrête toutes les conversions implicites. Seuls les arguments de inttype sont acceptés, tous les autres tenteront d'instancier une fonction "supprimée".
UncleBens