Czy praktyka zwracania zmiennej referencyjnej C++ jest zła?

To jest trochę subiektywne myślę, nie jestem pewien, czy opinia będzie jednomyślna(widziałem wiele fragmentów kodu, gdzie odniesienia są zwracane).

Zgodnie z komentarzem do to pytanie, które właśnie zadałem, dotyczące inicjalizacji referencji , zwracanie referencji może być złe, ponieważ [jak rozumiem] ułatwia to pominięcie jego usunięcia, co może prowadzić do wycieków pamięci.

To mnie martwi, gdyż podążałem za przykładami (chyba, że wyobrażam sobie rzeczy) i zrobiłem to w kilku miejscach... Źle zrozumiałem? Czy to jest złe? Jeśli tak, to jak bardzo złe?

Czuję, że z powodu mojego mieszanego worka wskaźników i odniesień, w połączeniu z faktem, że jestem nowy w C++, i całkowitym zamieszaniem co używać, Kiedy, moje aplikacje muszą być wyciek pamięci piekło...

Rozumiem również, że używanie inteligentnych / współdzielonych wskaźników jest ogólnie akceptowane jako najlepszy sposób na uniknięcie wycieków pamięci.

Author: Nick Bolton, 2009-04-15

16 answers

Ogólnie rzecz biorąc, zwracanie referencji jest całkowicie normalne i zdarza się cały czas.

Jeśli masz na myśli:

int& getInt() {
    int i;
    return i;  // DON'T DO THIS.
}
To wszelkiego rodzaju zło. Przydzielone stosy i znikną, a ty się do niczego nie odwołujesz. To też jest złe:
int& getInt() {
    int* i = new int;
    return *i;  // DON'T DO THIS.
}

Ponieważ teraz klient musi w końcu zrobić dziwne:

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

Zauważ, że referencje rvalue są nadal tylko referencjami, więc wszystkie złe aplikacje pozostają takie same.

Jeśli chcesz coś przeznaczyć jeśli nie jest to możliwe, należy użyć inteligentnego wskaźnika (lub w ogóle kontenera):

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

A teraz klient przechowuje inteligentny wskaźnik:

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

Odniesienia są również w porządku dla dostępu do rzeczy, w których wiesz, że życie jest utrzymywane otwarte na wyższym poziomie, np.:]}

struct immutableint {
    immutableint(int i) : i_(i) {}

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

Tutaj wiemy, że w porządku jest zwracać odwołanie do i_, ponieważ cokolwiek nas wywołuje zarządza żywotnością instancji klasy, więc i_ będzie żyć przynajmniej tak długa.

I oczywiście nie ma nic złego w just:

int getInt() {
   return 0;
}

Jeśli czas życia powinien być pozostawiony do rozmówcy, a ty po prostu obliczasz wartość.

Summary: w porządku jest zwracać referencję, jeśli okres życia obiektu nie zakończy się po wywołaniu.

 340
Author: GManNickG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-10 07:31:43

Nie. Nie, Nie, tysiąc razy nie.

Złem jest odniesienie do dynamicznie przydzielanego obiektu i utrata oryginalnego wskaźnika. Kiedy new obiekt bierzesz na siebie obowiązek posiadania gwarantowanego delete.

Ale spójrz na, np, operator<<: że musi zwrócić referencję, lub

cout << "foo" << "bar" << "bletch" << endl ;
Nie zadziała.
 58
Author: Charlie Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 16:52:34

Powinieneś zwrócić odniesienie do istniejącego obiektu, który nie zniknie natychmiast i w którym nie zamierzasz przenieść własności.

Nigdy nie zwracaj referencji do zmiennej lokalnej lub czegoś takiego, ponieważ nie będzie tam odniesienia.

Możesz zwrócić odwołanie do czegoś niezależnego od funkcji, czego nie spodziewasz się, że funkcja wywołująca weźmie odpowiedzialność za usunięcie. Tak jest w przypadku typowej funkcji operator[].

Jeśli jesteś tworząc coś, powinieneś zwrócić wartość lub wskaźnik (zwykły lub inteligentny). Wartość można zwracać dowolnie, ponieważ przechodzi ona do zmiennej lub wyrażenia w funkcji wywołującej. Nigdy nie zwracaj wskaźnika do zmiennej lokalnej, ponieważ zniknie.

 41
Author: David Thornley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-07 15:03:26

To nie jest zło. Podobnie jak wiele rzeczy w C++, jest to dobre, jeśli jest używane poprawnie, ale jest wiele pułapek, których powinieneś być świadomy podczas korzystania z niego (jak zwracanie odniesienia do zmiennej lokalnej).

Są dobre rzeczy, które można z nim osiągnąć (jak map[name] = "hello world")

 14
Author: Mehrdad Afshari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 16:51:23

Uważam, że odpowiedzi nie są zadowalające, więc dodam moje dwa grosze.

Przeanalizujmy następujące przypadki:

Błędne użycie

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

To jest oczywiście błąd

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

Użycie ze zmiennymi statycznymi

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

To prawda, ponieważ zmienne statyczne istnieją przez cały okres istnienia programu.

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

Jest to również dość powszechne przy implementacji Singleton wzór

Class Singleton
{
    public:
        static Singleton& instance()
        {
            static Singleton instance;
            return instance;
        };

        void printHello()
        {
             printf("Hello");
        };

}

Użycie:

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

Operatory

Kontenery bibliotek standardowych w dużym stopniu zależą od użycia operatorów zwracających referencję, na przykład

T & operator*();

Może być stosowany w następujących]}

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

Szybki dostęp do danych wewnętrznych

Są chwile, kiedy & mogą być używane do szybkiego dostępu do danych wewnętrznych

Class Container
{
    private:
        std::vector<int> m_data;

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

Z użyciem:

Container cont;
cont.data().push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1
Jednak może to prowadzić do takich pułapek jak ta:]}
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!
 13
Author: thorhunter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-27 10:24:38
"Zwracanie referencji jest złe, ponieważ, po prostu [jak rozumiem] sprawia, że łatwiej przegapić usunięcie "
Nieprawda. Zwracanie referencji nie oznacza semantyki własności. To dlatego, że to robisz:
Value& v = thing->getTheValue();

...nie oznacza, że posiadasz pamięć, o której mowa przez v;

Jednak jest to straszny kod:

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

Jeśli robisz coś takiego, ponieważ "nie wymagasz wskaźnika na tej instancji" to: 1) po prostu dereference wskaźnik, jeśli potrzebujesz odniesienia, i 2) w końcu trzeba wskaźnik, ponieważ trzeba dopasować nowy z delete, i trzeba wskaźnik do wywołania delete.

 10
Author: John Dibling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:44

Są dwa przypadki:

  • Const reference --dobry pomysł, czasami, szczególnie dla ciężkich obiektów lub klas proxy, optymalizacja kompilatora

  • Non-const reference --bad idea, sometimes, breaks encapsulations

Oba mają ten sam problem -- mogą potencjalnie wskazywać na zniszczony obiekt...

Zalecałbym używanie inteligentnych wskaźników w wielu sytuacjach, w których wymagane jest zwrócenie referencji / wskaźnika.

Zwróć również uwagę na po:

istnieje formalna zasada-Standard C++ (sekcja 13.3.3.1.4 jeśli jesteś zainteresowany) stwierdza, że tymczasowe może być związane tylko z odniesieniem const - jeśli próbujesz użyć odniesienia non-const, kompilator musi oznaczyć to jako błąd.

 7
Author: Sasha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 16:59:00

Nie tylko nie jest złe, ale czasami jest niezbędne. Na przykład implementacja operatora [] std::vector nie byłaby możliwa bez użycia zwracanej wartości odniesienia.

 4
Author: anon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 16:55:47

Return reference jest zwykle używany w przeciążaniu operatorów w C++ dla dużych obiektów, ponieważ zwracanie wartości wymaga operacji kopiowania.(w przeciążeniu peratora zwykle nie używamy wskaźnika jako wartości zwracanej)

Ale return reference może powodować problem z alokacją pamięci. Ponieważ odniesienie do wyniku zostanie przekazane z funkcji jako odniesienie do zwracanej wartości, wartość zwracana nie może być zmienną automatyczną.

Jeśli chcesz użyć referencji zwracającej, możesz użyć bufora obiektu statycznego. na przykład

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

W ten sposób można bezpiecznie zwrócić referencję.

Ale zawsze możesz użyć wskaźnika zamiast referencji do zwracania wartości w functiong.

 1
Author: MinandLucy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-20 18:55:47

Dodanie do zaakceptowanej odpowiedzi:

struct immutableint {
    immutableint(int i) : i_(i) {}

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

Argumentowałbym, że ten przykład jest nie w porządku i powinien być unikany, jeśli to możliwe. Dlaczego? Bardzo łatwo jest skończyć zzwisającym odniesieniem .

Aby zilustrować punkt na przykładzie:

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

Wejście do strefy zagrożenia:

Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!
 1
Author: AMA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-21 22:35:32

Myślę, że używanie referencji jako wartości zwracanej funkcji jest znacznie prostsze niż używanie wskaźnika jako wartości zwracanej funkcji. Po drugie, zawsze bezpiecznie byłoby używać zmiennej statycznej, do której odnosi się zwracana wartość.

 0
Author: Tony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-29 02:35:35

Najlepiej jest utworzyć obiekt i przekazać go jako parametr odniesienia/wskaźnika do funkcji, która przydziela tę zmienną.

Przydzielanie obiektu w funkcji i zwracanie go jako referencji lub wskaźnika (Wskaźnik jest jednak bezpieczniejszy) jest złym pomysłem ze względu na zwolnienie pamięci na końcu bloku funkcji.

 0
Author: Drezir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-05 07:56:14

Funkcja jako lvalue (aka, zwracanie referencji non-const) powinna zostać usunięta z C++. To strasznie nieintuicyjne. Scott Meyers chciał min () z takim zachowaniem.

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

Co nie jest tak naprawdę poprawą na

setmin (a, b, 0);
To drugie ma nawet większy sens.

Zdaję sobie sprawę, że funkcja jako lvalue jest ważna dla strumieni w stylu C++, ale warto zwrócić uwagę, że strumienie w stylu C++ są straszne. Nie tylko ja tak myślę... z tego co pamiętam Alexandrescu miał duży artykuł o tym, jak zrobić lepiej, i wierzę, że boost próbował również stworzyć lepszą metodę bezpiecznego typu I/O.

 -1
Author: Dan Olson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-15 19:52:47
    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 
     }
  };

Funkcja GetPtr może uzyskać dostęp do pamięci dynamicznej po usunięciu lub nawet obiektu null. Co może powodować złe wyjątki dostępu. Zamiast tego getter i setter powinny być zaimplementowane i zweryfikowane rozmiar przed powrotem.

 -1
Author: Amarbir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-14 01:46:56

Natknąłem się na prawdziwy problem, gdzie to było naprawdę złe. Zasadniczo programista zwrócił odniesienie do obiektu w wektorze. To było złe!!!

O pełnych szczegółach pisałem w styczniu: http://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html

 -2
Author: developresource,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-23 06:09:55

O strasznym kodzie:

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

Tak więc, rzeczywiście, Wskaźnik pamięci stracił po powrocie. Ale jeśli używasz shared_ptr w ten sposób:

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

Pamięć nie zostanie utracona po powrocie i zostanie uwolniona po przypisaniu.

 -13
Author: Anatoly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-23 06:10:29