Is de gewoonte om een C++-referentievariabele te retourneren slecht?

Dit is een beetje subjectief denk ik; Ik weet niet zeker of de mening unaniem zal zijn (ik heb veel codefragmenten gezien waarin verwijzingen worden geretourneerd).

Volgens een opmerking bij deze vraag die ik zojuist heb gesteld over initialiseren referenties, kan het teruggeven van een referentie slecht zijn omdat, [zoals ik begrijp] het gemakkelijker maakt om het verwijderen ervan over het hoofd te zien, wat kan leiden tot geheugenlekken.

Dit baart me zorgen, aangezien ik voorbeelden heb gevolgd (tenzij ik me dingen verbeeld) en dit op een paar plaatsen heb gedaan… Heb ik het verkeerd begrepen? Is het kwaad? Zo ja, hoe slecht?

Ik heb het gevoel dat vanwege mijn allegaartje van aanwijzingen en referenties, gecombineerd met het feit dat ik nieuw ben in C++ en totale verwarring over wat ik wanneer moet gebruiken, mijn applicaties een geheugenlek moeten zijn…

Ik begrijp ook dat het gebruik van slimme/gedeelde aanwijzers algemeen wordt beschouwd als de beste manier om geheugenlekken te voorkomen.


Antwoord 1, autoriteit 100%

Over het algemeen is het teruggeven van een referentie volkomen normaal en gebeurt dit voortdurend.

Als je bedoelt:

int& getInt() {
    int i;
    return i;  // DON'T DO THIS.
}

Dat is allerlei kwaad. De stack-toegewezen izal verdwijnen en je verwijst naar niets. Dit is ook slecht:

int& getInt() {
    int* i = new int;
    return *i;  // DON'T DO THIS.
}

Omdat de klant nu uiteindelijk het vreemde moet doen:

int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt;         // must delete...totally weird and  evil
int oops = getInt(); 
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original

Merk op dat rvalu-referenties nog steeds slechts referenties zijn, dus alle kwaadaardige toepassingen blijven hetzelfde.

Als je iets wilt toewijzen dat buiten het bereik van de functie valt, gebruik dan een slimme aanwijzer (of in het algemeen een container):

std::unique_ptr<int> getInt() {
    return std::make_unique<int>(0);
}

En nu slaat de client een slimme aanwijzer op:

std::unique_ptr<int> x = getInt();

Referenties zijn ook goed voor toegang tot dingen waarvan u weet dat de levensduur op een hoger niveau wordt opengehouden, bijvoorbeeld:

struct immutableint {
    immutableint(int i) : i_(i) {}
    const int& get() const { return i_; }
private:
    int i_;
};

Hier weten we dat het oké is om een verwijzing naar i_terug te sturen, omdat wat ons ook roept de levensduur van de klasse-instantie beheert, dus i_zal minstens zo lang leven.

En natuurlijk is er niets mis met gewoon:

int getInt() {
   return 0;
}

Als de levensduur aan de beller moet worden overgelaten, en u berekent alleen de waarde.

Samenvatting: het is prima om een referentie te retourneren als de levensduur van het object niet eindigt na de aanroep.


Antwoord 2, autoriteit 15%

Nee. Nee, nee, duizend keer nee.

Wat slecht is, is een verwijzing maken naar een dynamisch toegewezen object en de oorspronkelijke aanwijzer verliezen. Wanneer je een object newneemt, ga je de verplichting aan om een gegarandeerde deletete hebben.

Maar kijk bijvoorbeeld eens naar operator<<: die moeteen referentie teruggeven, of

cout << "foo" << "bar" << "bletch" << endl ;

werkt niet.


Antwoord 3, autoriteit 12%

U moet een verwijzing naar een bestaand object retourneren dat niet onmiddellijk verdwijnt en waarvan u niet van plan bent eigendom over te dragen.

Retourneer nooit een verwijzing naar een lokale variabele of iets dergelijks, omdat er niet naar verwezen kan worden.

Je kunt een verwijzing retourneren naar iets dat onafhankelijk is van de functie, waarvan je niet verwacht dat de aanroepende functie de verantwoordelijkheid voor het verwijderen op zich neemt. Dit is het geval voor de typische operator[]-functie.

Als je iets aan het maken bent, moet je een waarde of een aanwijzer (normaal of slim) retourneren. U kunt een waarde vrijelijk retourneren, omdat deze in een variabele of expressie in de aanroepende functie gaat. Retourneer nooit een aanwijzer naar een lokale variabele, omdat deze zal verdwijnen.


Antwoord 4, autoriteit 8%

Ik vind de antwoorden niet bevredigend, dus ik voeg mijn twee cent toe.

Laten we de volgende gevallen analyseren:

Onjuist gebruik

int& getInt()
{
    int x = 4;
    return x;
}

Dit is duidelijk een fout

int& x = getInt(); // will refer to garbage

Gebruik met statische variabelen

int& getInt()
{
   static int x = 4;
   return x;
}

Dit klopt, want statische variabelen zijn aanwezig gedurende de hele levensduur van een programma.

int& x = getInt(); // valid reference, x = 4

Dit is ook heel gebruikelijk bij het implementeren van het Singleton-patroon

Class Singleton
{
    public:
        static Singleton& instance()
        {
            static Singleton instance;
            return instance;
        };
        void printHello()
        {
             printf("Hello");
        };
}

Gebruik:

Singleton& my_sing = Singleton::instance(); // Valid Singleton instance
 my_sing.printHello();  // "Hello"

Operators

Standaard bibliotheekcontainers zijn sterk afhankelijk van het gebruik van operators die bijvoorbeeld referentie retourneren

T & operator*();

kan in het volgende worden gebruikt

std::vector<int> x = {1, 2, 3}; // create vector with 3 elements
std::vector<int>::iterator iter = x.begin(); // iterator points to first element (1)
*iter = 2; // modify first element, x = {2, 2, 3} now

Snelle toegang tot interne gegevens

Er zijn momenten waarop & kan worden gebruikt voor snelle toegang tot interne gegevens

Class Container
{
    private:
        std::vector<int> m_data;
    public:
        std::vector<int>& data()
        {
             return m_data;
        }
}

met gebruik:

Container cont;
cont.data().push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1

ECHTER, dit kan leiden tot een valkuil zoals deze:

Container* cont = new Container;
std::vector<int>& cont_data = cont->data();
cont_data.push_back(1);
delete cont; // This is bad, because we still have a dangling reference to its internal data!
cont_data[0]; // dangling reference!

Antwoord 5, autoriteit 3%

Het is niet slecht. Zoals zoveel dingen in C++ is het goed als het correct wordt gebruikt, maar er zijn veel valkuilen waar je op moet letten bij het gebruik ervan (zoals het retourneren van een verwijzing naar een lokale variabele).

Er zijn goede dingen mee te bereiken (zoals map[name] = “hello world”)


Antwoord 6, autoriteit 2%

“het retourneren van een referentie is slecht omdat,
gewoon [zoals ik het begrijp] maakt het het
gemakkelijker te missen om het te verwijderen”

Niet waar. Het retourneren van een referentie impliceert geen eigendomssemantiek. Dat wil zeggen, gewoon omdat je dit doet:

Value& v = thing->getTheValue();

…betekent niet dat u nu eigenaar bent van het geheugen waarnaar wordt verwezen door v;

Dit is echter een vreselijke code:

int& getTheValue()
{
   return *new int;
}

Als je zoiets doet omdat “je niet vereisen een aanwijzer op die instantie”dan: 1) dereferentie van de aanwijzer als je een verwijzing nodig hebt, en 2) je hebt uiteindelijk de aanwijzer nodig, omdat je een nieuwe moet matchen met een delete, en je hebt een aanwijzer om te verwijderen.


Antwoord 7, autoriteit 2%

Er zijn twee gevallen:

  • const-referentie –goed idee, soms, vooral voor zware objecten of proxyklassen, compileroptimalisatie

  • niet-const-referentie –slecht idee breekt soms inkapseling

Beide delen hetzelfde probleem — kunnen mogelijk verwijzen naar vernietigd object…

Ik zou aanraden om slimme aanwijzers te gebruiken voor veel situaties waarin je een verwijzing/aanwijzer moet retourneren.

Let ook op het volgende:

Er is een formele regel – de C++-standaard (paragraaf 13.3.3.1.4 als u geïnteresseerd bent) stelt dat een tijdelijke alleen kan worden gebonden aan een const-referentie – als u een niet-const-referentie probeert te gebruiken de compiler moet dit als een fout markeren.


Antwoord 8

Het is niet alleen niet slecht, het is soms ook essentieel. Het zou bijvoorbeeld onmogelijk zijn om de operator [] van std::vector te implementeren zonder een referentie-retourwaarde te gebruiken.


Antwoord 9

Toevoeging aan het geaccepteerde antwoord:

struct immutableint {
    immutableint(int i) : i_(i) {}
    const int& get() const { return i_; }
private:
    int i_;
};

Ik zou zeggen dat dit voorbeeld niet okéis en indien mogelijk moet worden vermeden. Waarom? Het is heel gemakkelijk om te eindigen met een bungelende referentie.

Om het punt te illustreren met een voorbeeld:

struct Foo
{
    Foo(int i = 42) : boo_(i) {}
    immutableint boo()
    {
        return boo_;
    }  
private:
    immutableint boo_;
};

het betreden van de gevarenzone:

Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!

Antwoord 10

retourreferentie wordt meestal gebruikt bij overbelasting van operators in C++ voor grote objecten, omdat het retourneren van een waarde een kopieerbewerking vereist. (bij peratoroverbelasting gebruiken we de aanwijzer meestal niet als retourwaarde)

Maar retourverwijzing kan geheugentoewijzingsproblemen veroorzaken. Omdat een verwijzing naar het resultaat uit de functie wordt doorgegeven als een verwijzing naar de retourwaarde, kan de retourwaarde geen automatische variabele zijn.

als u een terugkerende referentie wilt gebruiken, kunt u een buffer van een statisch object gebruiken.
bijvoorbeeld

const max_tmp=5; 
Obj& get_tmp()
{
 static int buf=0;
 static Obj Buf[max_tmp];
  if(buf==max_tmp) buf=0;
  return Buf[buf++];
}
Obj& operator+(const Obj& o1, const Obj& o1)
{
 Obj& res=get_tmp();
 // +operation
  return res;
 }

op deze manier kunt u veilig terugkerende referentie gebruiken.

Maar je kunt altijd pointer gebruiken in plaats van referentie voor het retourneren van waarde in functiong.


Antwoord 11

Ik denk dat het gebruik van referentie als de retourwaarde van de functie veel eenvoudiger is dan het gebruik van de aanwijzer als de retourwaarde van de functie.
Ten tweede zou het altijd veilig zijn om een statische variabele te gebruiken waarnaar de retourwaarde verwijst.


Antwoord 12

Het beste is om object te maken en door te geven als referentie / pointerparameter naar een functie die deze variabele toewijst.

Toewijzing van object in functie en het retourneren als een referentie of aanwijzer (aanwijzer is echter veiliger) is een slecht idee vanwege het vrijmaken van het geheugen aan het einde van het functieblok.


Antwoord 13

   Class Set {
    int *ptr;
    int size;
    public: 
    Set(){
     size =0;
         }
     Set(int size) {
      this->size = size;
      ptr = new int [size];
     }
    int& getPtr(int i) {
     return ptr[i];  // bad practice 
     }
  };

GetPTR-functie heeft toegang tot dynamisch geheugen na het verwijderen of zelfs een null-object. Die slechte toegangsuitzonderingen kan veroorzaken. In plaats daarvan moeten getter en setter worden geïmplementeerd en de grootte geverifieerd voordat ze terugkeren.


Antwoord 14

Functie als LVALUE (AKA, terugkeer van niet-const-referenties) moet uit C++ worden verwijderd. Het is vreselijk niet intuïtief. Scott Meyers wilde een min () met dit gedrag.

min(a,b) = 0;  // What???

wat is niet echt een verbetering op

setmin (a, b, 0);

Dit laatste is zelfs logischer.

Ik realiseer me die functie als Lvalue belangrijk is voor C++ Style-streams, maar het is de moeite waard om eruit te wijzen dat C++ Style-streams verschrikkelijk zijn. Ik ben niet de enige die dit denkt … Terwijl ik Recall AlexandRescu een groot artikel had over hoe beter te doen, en ik geloof dat Boost ook heeft geprobeerd een betere safe-i / O-methode te maken.


Antwoord 15

Ik kwam een ​​echt probleem tegen waar het inderdaad slecht was. In wezen keerde een ontwikkelaar een verwijzing naar een object in een vector terug. Dat was slecht !!!

De volledige details waarover ik in januari schreef: http://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html


Antwoord 16

Over vreselijke code:

int& getTheValue()
{
   return *new int;
}

Dus inderdaad, geheugenaanwijzer verloren na terugkeer. Maar als je shared_ptr zo gebruikt:

int& getTheValue()
{
   std::shared_ptr<int> p(new int);
   return *p->get();
}

Geheugen gaat niet verloren na terugkomst en komt vrij na toewijzing.

Other episodes