Vraag Wat zijn de basisregels en idioom voor overbelasting van de operator?


Opmerking: de antwoorden zijn gegeven in een specifieke bestelling, maar omdat veel gebruikers antwoorden sorteren op stemmen, in plaats van de tijd die ze kregen, is dit een index van de antwoorden in de volgorde waarin ze het meest logisch zijn:

(Opmerking: dit is bedoeld als invoer voor De veelgestelde vragen over C ++ van Stack Overflow. Als je kritiek wilt leveren op het idee om een ​​FAQ in dit formulier te geven, dan de posting op meta die dit allemaal heeft gestart zou de plek zijn om dat te doen. Antwoorden op die vraag worden gemonitord in de C ++ chatroom, waar het FAQ-idee in de eerste plaats is begonnen, dus uw antwoord zal waarschijnlijk worden gelezen door degenen die met het idee kwamen.)  


1841
2017-12-12 12:44


oorsprong


antwoorden:


Veelvoorkomende operators overbelasten

Het grootste deel van het werk bij overbelastingsoperators is ketelplaatcode. Dat is geen wonder, want operators zijn louter syntactische suikers, hun daadwerkelijke werk zou kunnen worden gedaan door (en wordt vaak doorgestuurd naar) duidelijke functies. Maar het is belangrijk dat u deze ketelplaatcode juist krijgt. Als je faalt, zal de code van je operator niet compileren of zal de code van je gebruikers niet compileren of zal de code van je gebruikers zich verrassend gedragen.

Toewijzingsexploitant

Over toewijzing valt veel te zeggen. Het meeste is er echter al in gezegd Veelgestelde vragen over kopiëren en kopiëren van GMan, dus ik zal het meeste hier overslaan, alleen een lijst met de perfecte toewijzingsoperator ter referentie:

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

Bitshift-operators (gebruikt voor Stream I / O)

De bitshift-operators << en >>Hoewel ze nog steeds worden gebruikt in hardware-interfaces voor de bitmanipulatiefuncties die ze van C erven, zijn ze in de meeste toepassingen dominanter geworden als overbelaste stream-invoer- en uitvoeroperators. Zie de onderstaande sectie over binaire rekenkundige operatoren voor meer informatie over overbelasting als operators voor bitmanipulatie. Voor het implementeren van uw eigen aangepaste indeling en ontleedlogica wanneer uw object wordt gebruikt met iostreams, gaat u door.

De streamoperators, een van de meest overbelaste operators, zijn binaire infix-operators waarvoor de syntaxis geen beperking oplegt aan de vraag of ze leden of niet-leden moeten zijn. Omdat ze hun linkerargument wijzigen (ze veranderen de status van de stream), moeten ze volgens de vuistregels geïmplementeerd worden als leden van het type van hun linker operand. De linker operanden zijn echter streams van de standaardbibliotheek en hoewel de meeste uitvoer- en invoerbewerkingsprogramma's die worden gedefinieerd door de standaardbibliotheek inderdaad zijn gedefinieerd als leden van de streamklassen, kunt u uitvoer- en invoerbewerkingen voor uw eigen typen implementeren. kan de streamtypen van de standaardbibliotheek niet wijzigen. Daarom moet u deze operatoren voor uw eigen typen implementeren als niet-lidfuncties. De canonieke vormen van de twee zijn deze:

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;
}

Bij de implementatie operator>>, handmatig instellen van de status van de stream is alleen nodig wanneer de meting zelf is geslaagd, maar het resultaat is niet wat zou worden verwacht.

Functievraagoperator

De operator functieaanroep, gebruikt om functieobjecten te maken, ook wel functoren genoemd, moet worden gedefinieerd als a lid functie, dus het heeft altijd de impliciete this argument van lidfuncties. Anders dan dit kan het overbelast worden om een ​​aantal aanvullende argumenten te nemen, inclusief nul.

Hier is een voorbeeld van de syntaxis:

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

Gebruik:

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

In de standaardbibliotheek C ++ worden functieobjecten altijd gekopieerd. Uw eigen functieobjecten moeten daarom goedkoop zijn om te kopiëren. Als een functieobject absoluut gegevens moet gebruiken die duur zijn om te kopiëren, is het beter om die gegevens ergens anders op te slaan en naar het functieobject te verwijzen.

Vergelijkingsoperatoren

De operator voor binaire infixvergelijking moet volgens de vuistregels worden geïmplementeerd als niet-lidfuncties1. De unaire prefix-ontkenning ! moet (volgens dezelfde regels) worden geïmplementeerd als een ledenfunctie. (maar het is meestal geen goed idee om het te overbelasten.)

De algoritmen van de standaardbibliotheek (bijv. std::sort()) en typen (bijv. std::map) zal altijd alleen maar verwachten operator< aanwezig zijn. echter, de gebruikers van jouw type zullen verwachten dat alle andere operatoren aanwezig zijn, dus als je definieert operator<, zorg ervoor dat u de derde fundamentele regel van overbelasting door de operator volgt en definieer ook alle andere booleaanse vergelijkingsoperatoren. De canonieke manier om ze te implementeren is deze:

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);}

Het belangrijkste om op te merken is dat slechts twee van deze operators daadwerkelijk iets doen, de anderen sturen gewoon hun argumenten door naar een van deze twee om het eigenlijke werk te doen.

De syntaxis voor het overbelasten van de resterende binaire Booleaanse operatoren (||, &&) volgt de regels van de vergelijkingsoperatoren. Hoe het ook is heel onwaarschijnlijk dat u hier een redelijk gebruik van zou vinden2.

1  Zoals met alle vuistregels, kunnen er soms ook redenen zijn om deze te overtreden. Als dat zo is, vergeet dan niet dat de linker operand van de binaire vergelijkingsoperatoren, die voor lidfuncties zal zijn *this, moet zijn constook. Dus een vergelijkingsoperator geïmplementeerd als een lidfunctie zou deze handtekening moeten hebben:

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

(Merk op const aan het einde.)

2  Opgemerkt moet worden dat de ingebouwde versie van || en && gebruik shortcut-semantiek. Hoewel de door de gebruiker gedefinieerde (want ze zijn syntactische suiker voor methodeaanroepen) geen shortcut-semantiek gebruiken. De gebruiker verwacht dat deze operatoren een shortcut-semantiek hebben, en hun code kan ervan afhangen. Daarom is het hoogst geadviseerd om ze NOOIT te definiëren.

Rekenkundige operatoren

Unary rekenkundige operatoren

De unaire increment- en decrementoperators hebben zowel een voorvoegsel als een postfixaroma. Om de een van de ander te vertellen, nemen de postfix-varianten een extra dummy int-argument. Als u increment of decrement overbelast, moet u altijd zowel de prefix- als de postfixversie implementeren. Hier is de canonieke implementatie van increment, decrement volgt dezelfde regels:

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

Merk op dat de postfix-variant is geïmplementeerd in termen van prefix. Merk ook op dat postfix een extra kopie maakt.2

Overbelasting van unaire minus en plus is niet erg gebruikelijk en wordt waarschijnlijk het beste vermeden. Indien nodig moeten ze waarschijnlijk overladen worden als ledenfuncties.

2  Merk ook op dat de postfix-variant meer werk doet en daarom minder efficiënt is in het gebruik dan de prefix-variant. Dit is een goede reden om in het algemeen de voorkeur te geven aan het prefix-increment ten opzichte van het postfix-increment. Compilers kunnen meestal het extra werk van postfix-increment voor ingebouwde typen weg optimaliseren, maar ze kunnen mogelijk niet hetzelfde doen voor door de gebruiker gedefinieerde typen (wat iets zou kunnen zijn dat zo onschuldig lijkt als een lijst-iterator). Als je eenmaal gewend bent om te doen i++, het wordt heel moeilijk om te onthouden om te doen ++i in plaats daarvan wanneer i is niet van een ingebouwd type (plus je zou code moeten veranderen bij het veranderen van een type), dus is het beter om er een gewoonte van te maken altijd het voorvoegsel te gebruiken, tenzij postfix expliciet nodig is.

Binaire rekenkundige operatoren

Voor de binaire rekenkundige operatoren, vergeet niet om de derde overbelasting van de basisregel operator te gehoorzamen: Als u verstrekt +, ook bieden +=, als je geeft -, laat dit niet weg -=, enz. Andrew Koenig zou de eerste geweest zijn om waar te nemen dat de samenstellers van samengestelde toewijzing kunnen worden gebruikt als een basis voor hun niet-samengestelde tegenhangers. Dat wil zeggen, operator + wordt geïmplementeerd in termen van +=, - wordt geïmplementeerd in termen van -= enz.

Volgens onze vuistregels, + en zijn metgezellen moeten niet-leden zijn, terwijl hun tegenhangers in de samengestelde toewijzing (+= enz.), veranderend hun linkerargument, zou een lid moeten zijn. Hier is de voorbeeldcode voor += en +, de andere binaire rekenkundige operatoren moeten op dezelfde manier worden geïmplementeerd:

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+= geeft het resultaat per referentie terug, terwijl operator+ geeft een kopie van het resultaat. Natuurlijk is het retourneren van een referentie meestal efficiënter dan het retourneren van een kopie, maar in het geval van operator+, er is geen manier om het kopiëren te maken. Wanneer je schrijft a + b, je verwacht dat het resultaat een nieuwe waarde is, daarom operator+ moet een nieuwe waarde retourneren.3 Merk ook op dat operator+ neemt de linker operand door kopiëren in plaats van door const. referentie. De reden hiervoor is hetzelfde als de reden waarvoor wordt aangegeven operator= zijn argument per exemplaar nemen.

De bitmanipulatie-operatoren ~  &  |  ^  <<  >> moet op dezelfde manier worden geïmplementeerd als de rekenkundige operatoren. Echter (behalve voor overbelasting << en >> voor uitvoer en invoer) zijn er zeer weinig redelijke gebruiksgevallen om deze te overbelasten.

3  Nogmaals, de les die hieruit moet worden getrokken, is dat a += b is over het algemeen efficiënter dan a + b en zou indien mogelijk de voorkeur moeten hebben.

Array-abonnementen

De array-subscript-operator is een binaire operator die als een klaslid moet worden geïmplementeerd. Het wordt gebruikt voor containerachtige typen die toegang tot hun gegevenselementen mogelijk maken met een sleutel. De canonieke vorm om deze te leveren is deze:

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

Tenzij u niet wilt dat gebruikers van uw klasse de geretourneerde gegevenselementen kunnen wijzigen operator[] (in dat geval kunt u de niet-const-variant weglaten), moet u altijd beide varianten van de operator opgeven.

Als bekend is dat value_type verwijst naar een ingebouwd type, moet de const-variant van de operator een kopie retourneren in plaats van een const-verwijzing.

Operators voor aanwijzerachtige typen

Voor het definiëren van uw eigen iterators of slimme aanwijzers, moet u de unary prefix dereference-operator overbelasten * en de binaire infix-aanwijzerlidtoegangsoperator ->:

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

Merk op dat ook deze bijna altijd zowel een const als een niet-const-versie nodig hebben. Voor de -> operator, als value_type is van class (of struct of union) type, een andere operator->() wordt recursief genoemd, tot een operator->() retourneert een waarde van niet-klassetype.

De operator voor eenmalig adres van een operator mag nooit overbelast worden.

Voor operator->*() zien deze vraag. Het wordt maar zelden gebruikt en dus zelden overbelast. In feite worden zelfs iterators niet overbelast.


Doorgaan met Conversieoperators


896
2017-12-12 12:47



De drie basisregels voor operatoroverbelasting in C ++

Als het gaat om overbelasting van de operator in C ++, zijn er drie basisregels die u moet volgen. Zoals met al dergelijke regels, zijn er inderdaad uitzonderingen. Soms zijn mensen van hen afgeweken en de uitkomst was geen slechte code, maar zulke positieve afwijkingen zijn er nauwelijks. Op zijn minst waren 99 van de 100 van dergelijke afwijkingen die ik heb gezien ongerechtvaardigd. Het kan echter net zo goed 999 van de 1000 zijn geweest. U kunt dus beter aan de volgende regels voldoen.

  1. Wanneer de betekenis van een operator niet duidelijk en onomstreden is, mag deze niet worden overbelast.  Geef in plaats daarvan een functie met een goed gekozen naam.
    Kort gezegd, de eerste en belangrijkste regel voor het overbelasten van operatoren, in wezen, zegt: Doe het niet. Dat lijkt misschien vreemd, omdat er veel te weten valt over overbelasting van de operator en daarom behandelen veel artikelen, boekhoofdstukken en andere teksten dit allemaal. Maar ondanks dit ogenschijnlijk voor de hand liggende bewijs, er zijn slechts verrassend weinig gevallen waarbij overbelasting door de operator passend is. De reden is dat het eigenlijk moeilijk is om de semantiek achter de toepassing van een operator te begrijpen, tenzij het gebruik van de operator in het toepassingsdomein bekend is en onbetwist. In tegenstelling tot wat veel mensen denken, is dit bijna nooit het geval.

  2. Houd u altijd aan de bekende semantiek van de operator.
    C ++ stelt geen beperkingen aan de semantiek van overbelaste operatoren. Uw compiler accepteert graag de code die het binaire bestand implementeert + operator om af te trekken van de rechteroperand. De gebruikers van zo'n operator zouden de uitdrukking echter nooit vermoeden a + b aftrekken a van b. Dit veronderstelt natuurlijk dat de semantiek van de operator in het toepassingsdomein onomstreden is.

  3. Zorg altijd voor alles uit een reeks gerelateerde bewerkingen.
    Exploitanten zijn aan elkaar gerelateerden naar andere operaties. Als uw type ondersteunt a + b, gebruikers verwachten te kunnen bellen a += book. Als het prefix-increment ondersteunt ++a, zullen ze verwachten a++ om ook te werken. Als ze kunnen controleren of a < b, ze zullen zeer zeker ook verwachten te kunnen controleren of a > b. Als ze jouw type kunnen kopiëren - ze verwachten dat de opdracht ook werkt.


Doorgaan met De beslissing tussen lid en niet-lid.


440
2017-12-12 12:45



De algemene syntaxis van overbelasting door de operator in C ++

U kunt de betekenis van operators voor ingebouwde typen in C ++ niet wijzigen, operators kunnen alleen worden overladen voor door de gebruiker gedefinieerde typen1. Dat wil zeggen dat ten minste één van de operanden van een door de gebruiker gedefinieerd type moet zijn. Net als bij andere overbelaste functies, kunnen operators slechts één keer worden overladen voor een bepaalde set parameters.

Niet alle operators kunnen overladen worden in C ++. Onder de operators die niet kunnen worden overbelast zijn: .  ::  sizeof  typeid  .* en de enige ternaire operator in C ++, ?: 

Onder de operators die overbelast kunnen worden in C ++ zijn deze:

  • rekenkundige operatoren: +  -  *  /  % en +=  -=  *=  /=  %= (alle binaire infix); +  - (voorvoegsel unary); ++  -- (unary prefix en postfix)
  • bitmanipulatie: &  |  ^  <<  >> en &=  |=  ^=  <<=  >>= (alle binaire infix); ~ (voorvoegsel unary)
  • booleaanse algebra: ==  !=  <  >  <=  >=  ||  && (alle binaire infix); ! (voorvoegsel unary)
  • geheugen management: new  new[]  delete  delete[]
  • impliciete conversieoperatoren
  • miscellany: =  []  ->  ->*  ,  (alle binaire infix); *  & (alle unaire prefix) () (functie-aanroep, n-ary infix)

Echter, het feit dat jij kan overload dit betekent allemaal niet jou moeten doen. Raadpleeg de basisregels voor overbelasting van de operator.

In C ++ worden operators overbelast in de vorm van functies met speciale namen. Net als bij andere functies kunnen overbelaste operatoren over het algemeen worden geïmplementeerd als een lidfunctie van het type van hun linker operand of als niet-lid functies. Of u vrij bent om te kiezen of verplicht om een ​​van beide te gebruiken, hangt van verschillende criteria af.2 Een unaire operator @3, toegepast op een object x, wordt aangeroepen, hetzij als operator@(x) of als x.operator@(). Een binaire infix-operator @, toegepast op de objecten x en y, wordt genoemd als operator@(x,y) of als x.operator@(y).4 

Operators die zijn geïmplementeerd als niet-lidfuncties zijn soms vrienden van het type van hun operand.

1  De term "door de gebruiker gedefinieerd" kan enigszins misleidend zijn. C ++ maakt het onderscheid tussen ingebouwde typen en door de gebruiker gedefinieerde typen. Tot de eerstgenoemde behoren bijvoorbeeld int, char en double; voor de laatste behoren alle types van struct, class, union en enum, inclusief die van de standaardbibliotheek, ook al zijn ze niet als zodanig gedefinieerd door gebruikers.

2  Dit is gedekt in een later deel van deze FAQ.

3  De @ is geen geldige operator in C ++, daarom gebruik ik het als tijdelijke aanduiding.

4  De enige ternaire operator in C ++ kan niet worden overbelast en de enige n-ary-operator moet altijd worden geïmplementeerd als lidfunctie.


Doorgaan met De drie basisregels voor operatoroverbelasting in C ++.


229
2017-12-12 12:46



De beslissing tussen lid en niet-lid

De binaire operatoren = (Opdracht), [] (array-abonnement), -> (lid toegang), evenals de n-ary ()(functie oproep) operator, moet altijd worden geïmplementeerd als lid functies, omdat de syntaxis van de taal dit vereist.

Andere operatoren kunnen worden geïmplementeerd als leden of als niet-leden. Sommigen van hen moeten echter meestal worden geïmplementeerd als niet-lidfuncties, omdat hun linkeroperand niet door u kan worden aangepast. De meest prominente hiervan zijn de invoer- en uitvoeroperators << en >>, waarvan de linker operanden stroomklassen zijn van de standaardbibliotheek die u niet kunt wijzigen.

Voor alle operatoren waar u ervoor moet kiezen om ze te implementeren als een lidfunctie of een niet-lidfunctie, gebruik de volgende vuistregels beslissen:

  1. Als het een is unaire operator, implementeer het als een lid functie.
  2. Als een binaire operator het behandelt beide operanden gelijk (het laat ze ongewijzigd), implementeer deze operator als een geen lid functie.
  3. Als een binaire operator dat doet niet behandel beide operands even (meestal zal het de linker operand veranderen), het kan handig zijn om er een te maken lid functie van het type van de linker operand, als deze toegang heeft tot de private delen van de operand.

Natuurlijk zijn er, zoals met alle vuistregels, uitzonderingen. Als je een type hebt

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

en je wilt de increment- en decrement-operatoren hiervoor overbelasten, je kunt dit niet doen als lid-functies, omdat in C ++, enum-typen geen lid-functies kunnen hebben. Dus je moet het overladen als een gratis functie. En operator<() voor een klassemalplaatje genest binnen een klassesjabloon is het veel gemakkelijker om te schrijven en te lezen als het wordt gedaan als een ledfunctie inline in de klassedefinitie. Maar dit zijn inderdaad zeldzame uitzonderingen.

(Echter, als je maakt een uitzondering, vergeet het probleem niet const-ness voor de operand die voor lidfuncties de impliciete wordt this argument. Als de operator als een niet-lid-functie het meest linkse argument zou nemen als een const referentie, dezelfde operator als een ledfunctie moet een const aan het einde te maken *this een const referentie.)


Doorgaan met Veelvoorkomende operators overbelasten.


211
2017-12-12 12:49



Conversieoperators (ook bekend als door de gebruiker gedefinieerde conversies)

In C ++ kunt u conversieoperators maken, operators waarmee de compiler tussen uw typen en andere gedefinieerde typen kan converteren. Er zijn twee soorten conversieoperators, impliciete en expliciete.

Implicit Conversion Operators (C ++ 98 / C ++ 03 en C ++ 11)

Een impliciete conversie-operator staat de compiler toe om impliciet te converteren (zoals de conversie ertussen int en long) de waarde van een door de gebruiker gedefinieerd type voor een ander type.

Het volgende is een eenvoudige klasse met een impliciete conversie-operator:

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

Impliciete conversie-operators, zoals constructors met één argument, zijn door de gebruiker gedefinieerde conversies. Compilers zullen één door de gebruiker gedefinieerde conversie verlenen wanneer geprobeerd wordt een oproep aan een overbelaste functie te koppelen.

void f(const char*);

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

In eerste instantie lijkt dit erg nuttig, maar het probleem hiermee is dat de impliciete conversie zelfs van start gaat wanneer dat niet wordt verwacht. In de volgende code, void f(const char*)wordt gebeld omdat my_string() is geen lvalue, dus de eerste komt niet overeen:

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

f(my_string());

Beginners krijgen dit gemakkelijk verkeerd en zelfs ervaren C ++ -programmeurs zijn soms verbaasd omdat de compiler een overbelasting kiest die ze niet vermoedden. Deze problemen kunnen worden verzacht door expliciete conversieoperators.

Expliciete conversieoperatoren (C ++ 11)

In tegenstelling tot impliciete conversieoperators zullen expliciete conversieoperators nooit instappen wanneer u ze niet verwacht. Het volgende is een eenvoudige klasse met een expliciete conversieoperator:

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

Let op de explicit. Wanneer u nu de onverwachte code probeert uit te voeren van de impliciete conversieoperatoren, krijgt u een compileerfout:

prog.cpp: In functie 'int main ()':
prog.cpp: 15: 18: fout: geen overeenkomende functie voor aanroep naar 'f (my_string)'
prog.cpp: 15: 18: opmerking: kandidaten zijn:
prog.cpp: 11: 10: let op: void f (my_string &)
prog.cpp: 11: 10: opmerking: geen bekende conversie voor argument 1 van 'my_string' naar 'my_string &'
prog.cpp: 12: 10: let op: void f (const char *)
prog.cpp: 12: 10: opmerking: geen bekende conversie voor argument 1 van 'mijn_reeks' in 'const char *'

Om de expliciete cast-operator aan te roepen, moet u gebruiken static_cast, een cast in C-stijl of een cast in constructorstijl (d.w.z. T(value) ).

Er is echter één uitzondering: de compiler mag impliciet converteren naar bool. Bovendien mag de compiler geen andere impliciete conversie uitvoeren nadat deze is geconverteerd naar bool (een compiler mag 2 impliciete conversies tegelijkertijd uitvoeren, maar slechts 1 door de gebruiker gedefinieerde conversie op max).

Omdat de compiler niet "voorbij" zal casten bool, expliciete conversieoperatoren verwijderen nu de behoefte aan de Safe Bool-idioom. Slimme aanwijzers voordat C ++ 11 bijvoorbeeld het Safe Bool-idioom gebruikte om conversies naar integrale typen te voorkomen. In C ++ 11 gebruiken de slimme aanwijzers in plaats daarvan een expliciete operator omdat de compiler niet impliciet naar een integraal type kan worden geconverteerd nadat het expliciet een type naar Bool heeft geconverteerd.

Doorgaan met overbelasting new en delete.


143
2018-05-17 18:32



overbelasting new en delete

Notitie: Dit gaat alleen over de syntaxis van overbelasting new en delete, niet met de implementatie van dergelijke overbelaste operatoren. Ik denk dat de semantiek van overbelasting new en delete verdienen hun eigen veelgestelde vragen, met het onderwerp operator overloading kan ik het nooit doen.

Basics

In C ++, wanneer je een schrijft nieuwe uitdrukking graag willen new T(arg) er gebeuren twee dingen wanneer deze uitdrukking wordt geëvalueerd: ten eerste operator new wordt aangeroepen om onbewerkt geheugen te verkrijgen en vervolgens de geschikte constructor van T wordt aangeroepen om van dit onbewerkte geheugen een geldig object te maken. Evenzo, wanneer u een object verwijdert, wordt eerst de destructor ervan aangeroepen en vervolgens wordt het geheugen teruggegeven operator delete.
Met C ++ kunt u beide bewerkingen afstemmen: geheugenbeheer en de constructie / vernietiging van het object in het toegewezen geheugen. Dit laatste wordt gedaan door constructeurs en destructors te schrijven voor een klas. Het afstemmen van het geheugenbeheer gebeurt door uw eigen geheugen te schrijven operator new en operator delete.

De eerste van de basisregels voor overbelasting van de operator - doe het niet - geldt met name voor overbelasting new en delete. Bijna de enige redenen om deze operators te overbelasten zijn prestatieproblemen en geheugen beperkingen, en in veel gevallen andere acties, zoals wijzigingen in de algoritmen gebruikt, levert veel op hogere kosten / winstverhouding dan proberen het geheugenbeheer aan te passen.

De standaardbibliotheek C ++ wordt geleverd met een set vooraf gedefinieerde new en delete operators. De belangrijkste zijn deze:

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(); 

De eerste twee toewijzen / deallocate geheugen voor een object, de laatste twee voor een array van objecten. Als u uw eigen versies hiervan verstrekt, zullen ze dat doen niet overbelasten, maar vervangen die van de standaardbibliotheek.
Als je overbelast operator new, je moet altijd ook de matching overbelasten operator delete, zelfs als u het nooit van plan bent te bellen. De reden is dat, als een constructor gooit tijdens de evaluatie van een nieuwe expressie, het runtime-systeem het geheugen terugstuurt naar de operator delete passend bij de operator new die was geroepen om het geheugen toe te wijzen om het object te maken. Als u geen overeenkomst opgeeft operator delete, de standaard wordt genoemd, wat bijna altijd fout is.
Als je overbelast new en delete, u zou ook moeten overwegen om de array-varianten te overbelasten.

Plaatsing new

Met C ++ kunnen nieuwe en verwijderen operators extra argumenten gebruiken.
Zogenaamde plaatsing nieuw stelt u in staat om een ​​object te maken op een bepaald adres dat wordt doorgegeven aan:

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

De standaardbibliotheek wordt geleverd met de juiste overbelastingen van de nieuwe en verwijder operators voor dit:

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(); 

Houd er rekening mee dat in de voorbeeldcode voor plaatsing nieuw hierboven vermeld, operator delete wordt nooit aangeroepen, tenzij de constructor van X een uitzondering genereert.

Je kunt ook overbelasten new en delete met andere argumenten. Net als bij het aanvullende argument voor plaatsing nieuw, staan ​​deze argumenten ook tussen haakjes achter het trefwoord new. Alleen om historische redenen worden dergelijke varianten vaak ook wel plaatsing nieuw genoemd, ook al zijn hun argumenten niet om een ​​object op een specifiek adres te plaatsen.

Klasse-specifiek nieuw en verwijderen

Meestal wilt u het geheugenbeheer nauwkeuriger afstemmen, omdat uit metingen is gebleken dat exemplaren van een specifieke klasse of een groep verwante klassen vaak worden gemaakt en vernietigd en dat het standaardgeheugenbeheer van het runtime-systeem, afgestemd op algemene prestaties, deals inefficiënt in dit specifieke geval. Om dit te verbeteren, kunt u nieuw voor een specifieke klasse overbelasten en verwijderen:

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);
    // ... 
}; 

Overloaded dus, gedragen new en delete zich als statische led-functies. Voor objecten van my_class, de std::size_t argument zal altijd zijn sizeof(my_class). Deze operatoren worden echter ook opgeroepen voor dynamisch toegewezen objecten van afgeleide klassen, in welk geval het groter kan zijn dan dat.

Globaal nieuw en verwijderen

Om de globale nieuwe en de overbelasting te overbelasten, vervangt u simpelweg de vooraf gedefinieerde operatoren van de standaardbibliotheek door de onze. Dit hoeft echter zelden te gebeuren.


130
2017-12-12 13:07



Waarom kan niet operator<< functie voor het streamen van objecten naar std::cout of een bestand een ledenfunctie zijn?

Laten we zeggen dat je:

struct Foo
{
   int a;
   double b;

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

Gegeven dat, kunt u niet gebruiken:

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

Sinds operator<< is overbelast als een lidfunctie van Foo, de LHS van de operator moet een zijn Foo voorwerp. Dat betekent dat u verplicht bent om:

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

wat niet erg intuïtief is.

Als u het definieert als een niet-lidfunctie,

struct Foo
{
   int a;
   double b;
};

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

Je kunt gebruiken:

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

wat heel intuïtief is.


29
2018-01-22 19:00