Question Quelles sont les règles de base et les idiomes pour la surcharge de l'opérateur?


Note: Les réponses ont été données en une commande spécifique, mais puisque de nombreux utilisateurs trient les réponses en fonction des votes, plutôt que le temps qu'ils ont été donnés, voici un index des réponses dans l'ordre dans lequel ils ont le plus de sens:

(Note: Ceci est censé être une entrée pour FAQ C ++ de Stack Overflow. Si vous voulez critiquer l'idée de fournir une FAQ dans ce formulaire, alors l'affichage sur meta qui a commencé tout cela serait l'endroit pour le faire. Les réponses à cette question sont suivies dans le Chatroom C ++, où l'idée de FAQ a commencé en premier lieu, ainsi votre réponse est très susceptible d'être lue par ceux qui ont eu l'idée.)  


1841
2017-12-12 12:44


origine


Réponses:


Opérateurs communs pour surcharger

La plupart du travail dans les opérateurs de surcharge est le code de la chaudière. Ce n'est pas étonnant, puisque les opérateurs ne sont que du sucre syntaxique, leur travail réel pourrait être fait par (et est souvent transmis à) des fonctions simples. Mais il est important que vous obteniez ce code de plaque de chaudière. Si vous échouez, le code de votre opérateur ne sera pas compilé ou le code de vos utilisateurs ne se compilera pas ou le code de vos utilisateurs se comportera de manière surprenante.

Opérateur d'assignation

Il y a beaucoup à dire sur l'affectation. Cependant, la plupart d'entre eux a déjà été dit dans La fameuse FAQ sur la copie et le remplacement de GMan, donc je passerai la majeure partie ici, en ne listant que l'opérateur d'assignation parfait pour référence:

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

Opérateurs Bitshift (utilisés pour les E / S de flux)

Les opérateurs de bitshift << et >>Bien qu'ils soient encore utilisés dans l'interfaçage matériel pour les fonctions de manipulation de bits héritées de C, ils sont devenus plus courants en tant qu'opérateurs d'entrée et de sortie de flux surchargés dans la plupart des applications. Pour la surcharge de guidage en tant qu'opérateurs de manipulation de bits, voir la section ci-dessous sur les opérateurs arithmétiques binaires. Pour implémenter votre propre format personnalisé et la logique d'analyse lorsque votre objet est utilisé avec iostreams, continuez.

Les opérateurs de flux, parmi les opérateurs les plus fréquemment surchargés, sont des opérateurs infixes binaires pour lesquels la syntaxe spécifie aucune restriction quant à savoir s'ils doivent être membres ou non-membres. Comme ils changent leur argument de gauche (ils modifient l'état du flux), ils devraient, selon les règles du pouce, être implémentés en tant que membres du type de leur opérande gauche. Cependant, leurs opérandes de gauche sont des flux de la bibliothèque standard et bien que la plupart des opérateurs de sortie et d'entrée de flux définis par la bibliothèque standard soient définis comme membres des classes de flux, vous implémentez des opérations de sortie et d'entrée pour vos propres types. ne peut pas changer les types de flux de la bibliothèque standard. C'est pourquoi vous devez implémenter ces opérateurs pour vos propres types en tant que fonctions non membres. Les formes canoniques des deux sont:

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // write obj to stream

  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // read obj from stream

  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);

  return is;
}

En mettant en œuvre operator>>, la définition manuelle de l'état du flux n'est nécessaire que lorsque la lecture elle-même a réussi, mais le résultat n'est pas celui attendu.

Opérateur d'appel de fonction

L'opérateur d'appel de fonction, utilisé pour créer des objets de fonction, également appelés foncteurs, doit être défini comme un membre fonction, donc il a toujours l'implicite this argument des fonctions membres. À part cela, il peut être surchargé pour prendre n'importe quel nombre d'arguments supplémentaires, y compris zéro.

Voici un exemple de la syntaxe:

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

Usage:

foo f;
int a = f("hello");

Dans toute la bibliothèque standard C ++, les objets de fonction sont toujours copiés. Vos propres objets de fonction devraient donc être bon marché à copier. Si un objet fonction a absolument besoin d'utiliser des données coûteuses à copier, il est préférable de stocker ces données ailleurs et de faire référence à l'objet fonction.

Opérateurs de comparaison

Les opérateurs de comparaison d'infixe binaire doivent, selon les règles empiriques, être implémentés en tant que fonctions non membres1. La négation du préfixe unaire ! devrait (selon les mêmes règles) être mis en œuvre en tant que fonction membre. (mais ce n'est généralement pas une bonne idée de le surcharger.)

Les algorithmes de la bibliothèque standard (par ex. std::sort()) et types (par ex. std::map) attendra toujours seulement operator< être présent. Cependant, le les utilisateurs de votre type s'attendent à ce que tous les autres opérateurs soient présentsaussi, si vous définissez operator<, veillez à suivre la troisième règle fondamentale de la surcharge de l'opérateur et définissez également tous les autres opérateurs de comparaison booléens. La façon canonique de les mettre en œuvre est la suivante:

inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}

La chose importante à noter ici est que seulement deux de ces opérateurs font réellement quelque chose, les autres transmettent simplement leurs arguments à l'un ou l'autre de ces deux pour faire le travail réel.

La syntaxe pour surcharger les opérateurs booléens binaires restants (||, &&) suit les règles des opérateurs de comparaison. Cependant, il est très peu probable que vous trouveriez un cas d'utilisation raisonnable pour ces2.

1  Comme avec toutes les règles générales, il peut parfois y avoir des raisons de briser celui-ci aussi. Si c'est le cas, n'oubliez pas que l'opérande de gauche des opérateurs de comparaison binaires, qui pour les fonctions membres sera *this, doit être const, aussi. Un opérateur de comparaison implémenté en tant que fonction membre devrait donc avoir cette signature:

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(Noter la const à la fin.)

2  Il convient de noter que la version intégrée de || et && utiliser la sémantique des raccourcis. Alors que l'utilisateur en a défini (parce qu'il s'agit de sucre syntaxique pour les appels de méthode), n'utilisez pas la sémantique des raccourcis. L'utilisateur s'attend à ce que ces opérateurs aient une sémantique de raccourci, et leur code peut en dépendre. Il est donc fortement conseillé de ne JAMAIS les définir.

Opérateurs arithmétiques

Opérateurs arithmétiques unaires

Les opérateurs unaire d'incrémentation et de décrémentation viennent à la fois en préfixe et en postfix. Pour distinguer l'une de l'autre, les variantes de suffixe prennent un argument int supplémentaire. Si vous surchargez ou diminuez, assurez-vous de toujours implémenter les versions de préfixe et de suffixe. Voici l'implémentation canonique de l'incrément, la décrémentation suit les mêmes règles:

class X {
  X& operator++()
  {
    // do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

Notez que la variante postfix est implémentée en termes de préfixe. Notez également que postfix fait une copie supplémentaire.2

La surcharge unaire moins et plus n'est pas très commune et probablement mieux évitée. Si nécessaire, ils devraient probablement être surchargés en tant que fonctions membres.

2  Notez également que la variante de suffixe fait plus de travail et est donc moins efficace à utiliser que la variante de préfixe. C'est une bonne raison de préférer généralement l'incrément de préfixe à l'incrément de suffixe. Alors que les compilateurs peuvent généralement optimiser le travail supplémentaire de l'incrément de suffixe pour les types intégrés, ils peuvent ne pas être en mesure de faire la même chose pour les types définis par l'utilisateur (qui pourrait ressembler à un itérateur de liste). Une fois que vous vous êtes habitué à faire i++, il devient très difficile de se souvenir de faire ++i Au lieu de cela i n'est pas de type intégré (plus vous devrez changer de code lorsque vous changez un type), il est donc préférable de prendre l'habitude d'utiliser toujours l'incrément de préfixe, à moins que postfix soit explicitement nécessaire.

Opérateurs arithmétiques binaires

Pour les opérateurs arithmétiques binaires, n'oubliez pas d'obéir à la surcharge du troisième opérateur de base: Si vous fournissez +, Fournissent également +=, si vous fournissez -, ne pas omettre -=Andrew Koenig aurait été le premier à remarquer que les opérateurs d'assignation composés peuvent être utilisés comme base pour leurs homologues non composés. C'est, opérateur + est mis en œuvre en termes de +=, - est mis en œuvre en termes de -= etc.

Selon nos règles générales, + et ses compagnons devraient être des non-membres, tandis que leurs homologues+= etc.), en changeant leur argument de gauche, devrait être un membre. Voici le code exemplaire pour += et +, les autres opérateurs arithmétiques binaires devraient être implémentés de la même manière:

class X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};
inline X operator+(X lhs, const X& rhs)
{
  lhs += rhs;
  return lhs;
}

operator+= renvoie son résultat par référence, tandis operator+ renvoie une copie de son résultat. Bien sûr, renvoyer une référence est généralement plus efficace que de retourner une copie, mais dans le cas de operator+, il n'y a aucun moyen de contourner la copie. Quand vous écrivez a + b, vous attendez que le résultat soit une nouvelle valeur, c'est pourquoi operator+ doit retourner une nouvelle valeur.3 Notez également que operator+ prend son opérande gauche par copie plutôt que par référence const. La raison en est la même que la raison donnant pour operator= prendre son argument par copie.

Les opérateurs de manipulation de bits ~  &  |  ^  <<  >> devrait être mis en œuvre de la même manière que les opérateurs arithmétiques. Cependant, (sauf pour la surcharge << et >> pour la sortie et l'entrée) il y a très peu de cas d'utilisation raisonnables pour les surcharger.

3  Encore une fois, la leçon à tirer de cela est que a += b est, en général, plus efficace que a + b et devrait être préféré si possible.

Array Subscription

L'opérateur d'indice de tableau est un opérateur binaire qui doit être implémenté en tant que membre de classe. Il est utilisé pour les types de type conteneur qui permettent l'accès à leurs éléments de données par une clé. La forme canonique de fournir ceux-ci est la suivante:

class X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

À moins que vous ne souhaitiez que les utilisateurs de votre classe puissent modifier les éléments de données renvoyés par operator[] (auquel cas vous pouvez omettre la variante non-const), vous devez toujours fournir les deux variantes de l'opérateur.

Si value_type est connu pour faire référence à un type prédéfini, la variante const de l'opérateur doit renvoyer une copie au lieu d'une référence const.

Opérateurs pour les types de type pointeur

Pour définir vos propres itérateurs ou pointeurs intelligents, vous devez surcharger l'opérateur de déréférencement du préfixe unaire * et l'opérateur d'accès au membre du pointeur infixe binaire ->:

class my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

Notez que ceux-ci auront aussi presque toujours besoin d'une version const et non-const. Pour le -> opérateur, si value_type est de class (ou struct ou union) type, un autre operator->() est appelé récursivement, jusqu'à ce qu'un operator->() renvoie une valeur de type non-classe.

L'adresse unaire de l'opérateur ne devrait jamais être surchargé.

Pour operator->*() voir cette question. Il est rarement utilisé et donc rarement surchargé. En fait, même les itérateurs ne le surchargent pas.


Continuer à Opérateurs de conversion


896
2017-12-12 12:47



Les trois règles de base de la surcharge des opérateurs en C ++

Quand il s'agit de la surcharge de l'opérateur en C ++, il y a trois règles de base que vous devriez suivre. Comme avec toutes ces règles, il y a effectivement des exceptions. Parfois, les gens ont dévié d'eux et le résultat n'était pas un mauvais code, mais de tels écarts positifs sont rares. À tout le moins, 99 des 100 déviations de ce genre que j'ai vues étaient injustifiées. Cependant, il aurait tout aussi bien pu être 999 sur 1000. Donc, vous feriez mieux de respecter les règles suivantes.

  1. Chaque fois que la signification d'un opérateur n'est pas claire et incontestée, elle ne doit pas être surchargée.  Au lieu de cela, fournissez une fonction avec un nom bien choisi.
    Fondamentalement, la première et principale règle pour les opérateurs de surcharge, en son coeur, dit: Ne le fais pas. Cela peut sembler étrange, car il y a beaucoup à savoir sur la surcharge des opérateurs et donc beaucoup d'articles, chapitres de livres, et d'autres textes traitent de tout cela. Mais malgré cette évidence apparemment évidente, il y a seulement étonnamment peu de cas où la surcharge de l'opérateur est appropriée. La raison en est qu'il est en fait difficile de comprendre la sémantique derrière l'application d'un opérateur à moins que l'utilisation de l'opérateur dans le domaine d'application soit bien connue et incontestée. Contrairement à la croyance populaire, ce n'est presque jamais le cas.

  2. Toujours coller à la sémantique bien connue de l'opérateur.
    C ++ ne pose aucune limitation sur la sémantique des opérateurs surchargés. Votre compilateur acceptera volontiers le code qui implémente le binaire + opérateur à soustraire de son opérande droit. Cependant, les utilisateurs d'un tel opérateur ne soupçonneraient jamais l'expression a + b soustraire a de b. Bien sûr, cela suppose que la sémantique de l'opérateur dans le domaine d'application est incontestée.

  3. Fournissez toujours tout d'un ensemble d'opérations connexes.
    Les opérateurs sont liés les uns aux autreset à d'autres opérations. Si votre type prend en charge a + b, les utilisateurs s'attendent à pouvoir appeler a += b, aussi. S'il supporte l'incrément du préfixe ++a, ils vont s'attendre a++ travailler aussi. S'ils peuvent vérifier si a < b, ils s'attendent certainement aussi à pouvoir vérifier si a > b. S'ils peuvent copier-construire votre type, ils s'attendent à ce que l'affectation fonctionne également.


Continuer à La décision entre le membre et le non-membre.


440
2017-12-12 12:45



La syntaxe générale de la surcharge de l'opérateur en C ++

Vous ne pouvez pas changer la signification des opérateurs pour les types intégrés en C ++, les opérateurs ne peuvent être surchargés que pour les types définis par l'utilisateur1. C'est-à-dire qu'au moins l'un des opérandes doit être d'un type défini par l'utilisateur. Comme avec d'autres fonctions surchargées, les opérateurs peuvent être surchargés pour un certain ensemble de paramètres qu'une seule fois.

Tous les opérateurs ne peuvent pas être surchargés en C ++. Parmi les opérateurs qui ne peuvent pas être surchargés sont: .  ::  sizeof  typeid  .* et le seul opérateur ternaire en C ++, ?: 

Parmi les opérateurs qui peuvent être surchargés en C ++, on trouve:

  • opérateurs arithmétiques: +  -  *  /  % et +=  -=  *=  /=  %= (tout l'infixe binaire); +  - (préfixe unaire); ++  -- (préfixe unaire et postfixe)
  • manipulation de bits: &  |  ^  <<  >> et &=  |=  ^=  <<=  >>= (tout l'infixe binaire); ~ (préfixe unaire)
  • Algèbre de Boole: ==  !=  <  >  <=  >=  ||  && (tout l'infixe binaire); ! (préfixe unaire)
  • gestion de la mémoire: new  new[]  delete  delete[]
  • opérateurs de conversion implicites
  • recueil: =  []  ->  ->*  ,  (tout l'infixe binaire); *  & (tout préfixe unaire) () (appel de fonction, infixe n-aire)

Cependant, le fait que vous pouvez surcharger tout cela ne signifie pas que vous devrait fais-le. Voir les règles de base de la surcharge de l'opérateur.

En C ++, les opérateurs sont surchargés sous la forme de fonctions avec des noms spéciaux. Comme avec d'autres fonctions, les opérateurs surchargés peuvent généralement être mis en œuvre soit comme fonction membre du type de leur opérande gauche ou comme fonctions non-membres. Si vous êtes libre de choisir ou lié à utiliser l'un dépend de plusieurs critères.2 Un opérateur unaire @3, appliqué à un objet x, est invoqué soit comme operator@(x) ou comme x.operator@(). Un opérateur d'infixe binaire @, appliqué aux objets x et y, est appelé soit comme operator@(x,y) ou comme x.operator@(y).4 

Les opérateurs qui sont implémentés en tant que fonctions non membres sont parfois des amis du type de leur opérande.

1  Le terme "défini par l'utilisateur" peut être légèrement trompeur. C ++ fait la distinction entre les types intégrés et les types définis par l'utilisateur. Aux premiers appartiennent par exemple int, char, et double; à ce dernier appartient tous les types struct, class, union et enum, y compris ceux de la bibliothèque standard, même s'ils ne sont pas, en tant que tels, définis par les utilisateurs.

2  Ceci est couvert dans une partie plus tard de cette FAQ.

3  le @ n'est pas un opérateur valide en C ++, c'est pourquoi je l'utilise comme espace réservé.

4  Le seul opérateur ternaire en C ++ ne peut pas être surchargé et le seul opérateur n-aire doit toujours être implémenté en tant que fonction membre.


Continuer à Les trois règles de base de la surcharge des opérateurs en C ++.


229
2017-12-12 12:46



La décision entre le membre et le non-membre

Les opérateurs binaires = (affectation), [] (abonnement à un groupe), -> (accès membre), ainsi que le n-aire ()(appel de fonction), doit toujours être implémenté comme fonctions membres, parce que la syntaxe du langage l'exige.

D'autres opérateurs peuvent être implémentés en tant que membres ou en tant que non-membres. Certains d'entre eux, cependant, doivent généralement être implémentés en tant que fonctions non membres, car leur opérande gauche ne peut pas être modifié par vous. Les plus importants d'entre eux sont les opérateurs d'entrée et de sortie << et >>, dont les opérandes de gauche sont des classes de flux de la bibliothèque standard que vous ne pouvez pas changer.

Pour tous les opérateurs où vous devez choisir de les implémenter en tant que fonction membre ou non-membre, utilisez les règles générales suivantes decider:

  1. Si c'est un opérateur unaire, le mettre en œuvre en tant que membre fonction.
  2. Si un opérateur binaire traite les deux opérandes également (il les laisse inchangés), implémenter cet opérateur comme non membre fonction.
  3. Si un opérateur binaire fait ne pas traiter ses deux opérandes également (habituellement il va changer son opérande gauche), il pourrait être utile d'en faire un membre fonction de son type d'opérande gauche, si elle doit accéder aux parties intimes de l'opérande.

Bien sûr, comme toutes les règles générales, il y a des exceptions. Si vous avez un type

enum Month {Jan, Feb, ..., Nov, Dec}

et vous voulez surcharger les opérateurs d'incrémentation et de décrémentation, vous ne pouvez pas le faire en tant que membre, car en C ++, les types enum ne peuvent pas avoir de fonctions membres. Vous devez donc le surcharger en tant que fonction gratuite. Et operator<() pour un modèle de classe imbriqué dans un modèle de classe est beaucoup plus facile à écrire et à lire lorsqu'il est fait comme une fonction membre en ligne dans la définition de classe. Mais ce sont en effet de rares exceptions.

(Toutefois, si vous faites une exception, n'oubliez pas la question de const-ness pour l'opérande qui, pour les fonctions membres, devient implicite this argument. Si l'opérateur en tant que fonction non membre prenait son argument le plus à gauche en tant que const référence, le même opérateur en tant que fonction membre doit avoir un const à la fin de faire *this une const référence.)


Continuer à Opérateurs communs pour surcharger.


211
2017-12-12 12:49



Opérateurs de conversion (également appelés conversions définies par l'utilisateur)

En C ++, vous pouvez créer des opérateurs de conversion, des opérateurs qui permettent au compilateur de convertir entre vos types et d'autres types définis. Il existe deux types d'opérateurs de conversion, implicite et explicite.

Opérateurs de conversion implicites (C ++ 98 / C ++ 03 et C ++ 11)

Un opérateur de conversion implicite permet au compilateur de convertir implicitement (comme la conversion entre int et long) la valeur d'un type défini par l'utilisateur à un autre type.

Voici une classe simple avec un opérateur de conversion implicite:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

Les opérateurs de conversion implicites, comme les constructeurs à un seul argument, sont des conversions définies par l'utilisateur. Les compilateurs accordent une conversion définie par l'utilisateur lorsqu'ils tentent de faire correspondre un appel à une fonction surchargée.

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

Au début, cela semble très utile, mais le problème avec cela est que la conversion implicite intervient même quand elle n'est pas attendue. Dans le code suivant, void f(const char*)sera appelé parce que my_string() n'est pas un lvalue, donc le premier ne correspond pas:

void f(my_string&);
void f(const char*);

f(my_string());

Les débutants se trompent facilement et même les programmeurs C ++ expérimentés sont parfois surpris parce que le compilateur choisit une surcharge qu'ils ne soupçonnaient pas. Ces problèmes peuvent être atténués par des opérateurs de conversion explicites.

Opérateurs de conversion explicites (C ++ 11)

Contrairement aux opérateurs de conversion implicites, les opérateurs de conversion explicites ne seront jamais actifs lorsque vous ne les attendez pas. Voici une classe simple avec un opérateur de conversion explicite:

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

Notez le explicit. Maintenant, lorsque vous essayez d'exécuter le code inattendu à partir des opérateurs de conversion implicites, vous obtenez une erreur de compilation:

prog.cpp: Dans la fonction 'int main ()':
prog.cpp: 15: 18: erreur: aucune fonction correspondante pour l'appel à 'f (my_string)'
prog.cpp: 15: 18: note: les candidats sont:
prog.cpp: 11: 10: note: void f (my_string &)
prog.cpp: 11: 10: note: aucune conversion connue pour l'argument 1 de 'my_string' à 'my_string &'
prog.cpp: 12: 10: note: void f (const char *)
prog.cpp: 12: 10: note: aucune conversion connue pour l'argument 1 de 'my_string' à 'const char *'

Pour appeler l'opérateur de distribution explicite, vous devez utiliser static_cast, une distribution de style C ou une distribution de style constructeur (c.-à-d. T(value) ).

Cependant, il y a une exception à cela: le compilateur est autorisé à convertir implicitement en bool. En outre, le compilateur n'est pas autorisé à effectuer une autre conversion implicite après sa conversion en bool (un compilateur est autorisé à faire 2 conversions implicites à la fois, mais seulement 1 conversion définie par l'utilisateur à max).

Parce que le compilateur ne lancera pas "passé" bool, les opérateurs de conversion explicites suppriment désormais le besoin de Safe idiome de Bool. Par exemple, les pointeurs intelligents avant C ++ 11 utilisaient l'idiome Safe Bool pour empêcher les conversions en types entiers. En C ++ 11, les pointeurs intelligents utilisent un opérateur explicite à la place parce que le compilateur n'est pas autorisé à convertir implicitement en un type intégral après avoir explicitement converti un type en booléen.

Continuer à Surcharge new et delete.


143
2018-05-17 18:32



Surcharge new et delete

Remarque: Cela ne concerne que le syntaxe de surcharge new et delete, pas avec le la mise en oeuvre de tels opérateurs surchargés. Je pense que la sémantique de la surcharge new et delete méritent leur propre FAQ, dans le sujet de la surcharge de l'opérateur, je ne peux jamais le faire justice.

Notions de base

En C ++, quand vous écrivez un nouvelle expression comme new T(arg) deux choses se produisent lorsque cette expression est évaluée: operator new est invoqué pour obtenir de la mémoire brute, puis le constructeur approprié de T est invoqué pour transformer cette mémoire brute en un objet valide. De même, lorsque vous supprimez un objet, son destructeur est d'abord appelé, puis la mémoire est renvoyée à operator delete.
C ++ vous permet de régler ces deux opérations: la gestion de la mémoire et la construction / destruction de l'objet dans la mémoire allouée. Ce dernier est fait en écrivant des constructeurs et des destructeurs pour une classe. La gestion de la mémoire de réglage fin est effectuée en écrivant votre propre operator new et operator delete.

La première des règles de base de la surcharge de l'opérateur - ne le fais pas - s'applique particulièrement à la surcharge new et delete. Presque les seules raisons de surcharger ces opérateurs sont problèmes de performance et contraintes de mémoire, et dans de nombreux cas, d'autres actions, comme changements aux algorithmes utilisé, fournira beaucoup rapport coût / gain plus élevé que d'essayer de modifier la gestion de la mémoire.

La bibliothèque standard C ++ est livrée avec un ensemble de prédéfinis new et delete les opérateurs. Les plus importants sont ceux-ci:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

Les deux premiers allouer / désallouer la mémoire pour un objet, les deux derniers pour un tableau d'objets. Si vous fournissez vos propres versions de ceux-ci, ils pas surcharger, mais remplacer ceux de la bibliothèque standard.
Si vous surchargez operator new, vous devriez toujours surcharger l'appariement operator deletemême si vous n'avez jamais l'intention de l'appeler. La raison en est que, si un constructeur lance pendant l'évaluation d'une nouvelle expression, le système d'exécution retournera la mémoire à operator delete correspondant à la operator new qui a été appelé pour allouer la mémoire pour créer l'objet. Si vous ne fournissez pas de correspondance operator delete, celui par défaut est appelé, ce qui est presque toujours faux.
Si vous surchargez new et delete, vous devriez également envisager de surcharger les variantes de tableau.

Placement new

C ++ permet aux opérateurs nouveaux et supprimés de prendre des arguments supplémentaires.
Le soi-disant placement nouveau vous permet de créer un objet à une certaine adresse qui est passée à:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

La bibliothèque standard contient les surcharges appropriées des opérateurs new et delete pour ceci:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

Notez que, dans l'exemple de code pour le placement nouveau donné ci-dessus, operator delete n'est jamais appelé, à moins que le constructeur de X ne lève une exception.

Vous pouvez également surcharger new et delete avec d'autres arguments. Comme avec l'argument supplémentaire pour le placement nouveau, ces arguments sont également répertoriés entre parenthèses après le mot-clé new. Simplement pour des raisons historiques, de telles variantes sont souvent appelées placement new, même si leurs arguments ne sont pas pour placer un objet à une adresse spécifique.

Nouvelle classe et supprimer

Le plus souvent, vous souhaiterez affiner la gestion de la mémoire car la mesure a montré que les instances d'une classe spécifique ou d'un groupe de classes connexes sont souvent créées et détruites et que la gestion de la mémoire par défaut du système d'exécution est réglée performances générales, traite inefficacement dans ce cas particulier. Pour améliorer cela, vous pouvez surcharger new et supprimer pour une classe spécifique:

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

Surchargé ainsi, new et delete se comportent comme des fonctions membres statiques. Pour les objets de my_class, la std::size_t argument sera toujours sizeof(my_class). Cependant, ces opérateurs sont également appelés pour les objets dynamiquement alloués classes dérivées, auquel cas il pourrait être plus grand que cela.

Global nouveau et supprimer

Pour surcharger le global new et delete, il suffit de remplacer les opérateurs prédéfinis de la bibliothèque standard par les nôtres. Cependant, cela doit rarement être fait.


130
2017-12-12 13:07



Pourquoi ne peut pas operator<< fonction pour diffuser des objets à std::cout ou à un fichier être une fonction membre?

Disons que vous avez:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

Compte tenu de cela, vous ne pouvez pas utiliser:

Foo f = {10, 20.0};
std::cout << f;

Depuis operator<< est surchargé en tant que fonction de membre Foo, le LHS de l'opérateur doit être un Foo objet. Ce qui signifie que vous devrez utiliser:

Foo f = {10, 20.0};
f << std::cout

ce qui est très non-intuitif.

Si vous le définissez comme une fonction non-membre,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

Vous pourrez utiliser:

Foo f = {10, 20.0};
std::cout << f;

ce qui est très intuitif.


29
2018-01-22 19:00