Ce operatori trebuie declarați prieteni? (Programare, C++, Supraîncărcarea Operatorului)

Rafael S. Calsaverini a intrebat.

În unele cărți și adesea pe internet văd recomandări de genul „operator== ar trebui să fie declarat ca prieten”.

Cum ar trebui să înțeleg când un operator trebuie să fie declarat ca prieten și când trebuie declarat ca membru? Care sunt operatorii care vor trebui cel mai adesea să fie declarați ca friends în afară de == și <<?

Comentarii

  • Funcțiile nemembre care au nevoie de acces la membrii privați ai clasei trebuie să fie friends. –  > Por AJG85.
  • @AJG85: Adevărat, dar cred că întrebarea este mai degrabă când să alegi membrii și când să alegi non-membrii (făcându-i prieteni dacă este necesar). –  > Por Mike Seymour.
  • We’ll că este un pic subiectiv, dar eu urmez sfatul lui Scott Meyers și încerc să prefer funcțiile de non-membru neprieten atunci când este posibil. Atunci aș prefera o funcție de membru în locul unei funcții de prieten în majoritatea cazurilor. Cel mai bun caz pentru o funcție prietenă este probabil în cazul în care doriți o interfață publică care are o promovare implicită a părții stângi. –  > Por AJG85.
4 răspunsuri
Chris Frederick

Acest lucru depinde cu adevărat de faptul dacă o clasă va fi în partea stângă sau dreaptă a apelului la operator== (sau alt operator). Dacă o clasă se va afla în partea dreaptă a expresiei – și nu oferă o conversie implicită la un tip care poate fi comparat cu partea stângă – trebuie să implementați operator== ca o funcție separată sau ca o clasă friend a clasei. În cazul în care operatorul trebuie să acceseze date private ale clasei, acesta trebuie să să fie declarat ca un friend.

De exemplu,

class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
};

vă permite să comparați un mesaj cu un șir de caractere

Message message("Test");
std::string msg("Test");
if (message == msg) {
    // do stuff...
}

dar nu și invers

    if (msg == message) { // this won't compile

Trebuie să declarați un prieten operator== în interiorul clasei

class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
    friend bool operator==(const std::string& lhs, const Message& rhs);
};

sau să declarați un operator de conversie implicit la tipul corespunzător

class Message {
    std::string content;
public:
    Message(const std::string& str);
    bool operator==(const std::string& rhs) const;
    operator std::string() const;
};

sau să declarați o funcție separată, care nu trebuie să fie prietenă dacă nu accesează date private ale clasei.

bool operator==(const std::string& lhs, const Message& rhs);

Comentarii

  • Un răspuns bun, dar nu este necesar să declarați funcția prietenă în afara definiției clasei. O singură friend declarație în interiorul clasei (cu o definiție inline, dacă doriți) este suficientă. –  > Por Mike Seymour.
  • @Mike Huh, aveți dreptate. Dintr-un motiv oarecare, nu am crezut că aceasta este o sintaxă validă… –  > Por Chris Frederick.
Alexander Gessler

Atunci când aveți operatorii în afara clasei, ambii parametri pot participa la conversii implicite de tip (în timp ce în cazul în care operatorii sunt definiți în corpul clasei, doar operanzii din dreapta pot participa). În general, acesta este un avantaj pentru toți operatorii binari clasici (de ex. ==,!=, +, -, <<, … ).

Bineînțeles, trebuie să declarați numai operatorii friendai clasei dvs. dacă este necesar și nu dacă aceștia își calculează rezultatul numai pe baza membrilor publici ai clasei.

CB Bailey

În general, numai operatorii care sunt implementați ca funcții libere și care au cu adevărat nevoie să acceseze date private sau protejate ale clasei pe care operează ar trebui să fie declarați ca prieteni, altfel ar trebui să fie doar funcții nemembre neprietenoase.

În general, singurii operatori pe care îi implementez ca funcții membre sunt cei care sunt în mod fundamental asimetrici și în cazul în care operanzii nu au roluri echivalente. Cei pe care tind să îi implementez ca membri sunt cei care trebuie să fie membri: atribuire simplă, (), [] și -> împreună cu operatorii de atribuire compusă, operatorii unari și poate unele supraîncărcări ale lui << și >> pentru clasele care sunt ele însele clase de flux sau clase de tip flux. Nu supraîncarc niciodată &&, || sau ,.

Toți ceilalți operatori tind să îi implementez ca funcții libere, de preferință folosind interfața publică a claselor pe care operează, revenind la a fi prieteni doar atunci când este necesar.

Păstrarea unor operatori precum !=, ==, <, +, /, etc. ca funcții nemembre permite tratarea identică a operanzilor din stânga și din dreapta în ceea ce privește secvențele de conversie implicite, ceea ce contribuie la reducerea numărului de asimetrii surprinzătoare.

Denis Yaroshevskiy

Nimeni nu a menționat hidden friends idiom ceea ce bănuiesc că este ceea ce vor să spună cărțile.

Versiunea lungă: https://www.justsoftwaresolutions.co.uk/cplusplus/hidden-friends.html

Versiunea scurtă:

Operatorii sunt găsiți prin ADL (argument dependent lookup) de cele mai multe ori. Acesta este modul în care un operator== definit pentru std::string în std este găsit atunci când nu vă aflați în spațiul de nume std.

Una dintre problemele comune ale operatorilor este un set gigantic de supraîncărcări. Puteți vedea adesea acest lucru în mesajele de eroare dacă încercați să utilizați operator<< pentru ceva ce nu este imprimabil.

Deci, dacă declarați operator== în spațiul de nume care conține clasa direct, va funcționa, dar va participa, de asemenea, la toate rezoluțiile de supraîncărcare din acel spațiu de nume, ceea ce va încetini compilarea și vă va da mai mult zgomot în erori.

Prezentarea prietenilor ascunși:

struct X {
  friend bool operator==(const X& x, const X& y) {...}
};

Acest operator== va fi luat în considerare pentru rezolvarea supraîncărcării numai dacă unul dintre operanzi are tipul X. În toate celelalte cazuri, nu va fi văzut, astfel încât compilarea va fi mai rapidă și mesajele de eroare mai bune.

Același lucru este valabil pentru toți operatorii cu doi operanzi, cum ar fi operator<< și, de asemenea, alte funcții destinate ADL, cum ar fi swap.

Întotdeauna îmi definesc operatorii în acest fel și este considerată o bună practică de către destul de mulți oameni în zilele noastre.

Singurul dezavantaj este că nu există o modalitate foarte bună de a-l defini în afara liniei. Poate doriți să vă gândiți la trimiterea la o funcție privată. sau puteți face acest lucru: https://godbolt.org/z/hMarb4 – dar asta înseamnă că cel puțin într-un fișier cpp, funcția operator== va participa la căutarea normală.