2013-06-21 30 views
5

Podczas pytając this question, dowiedziałem const odniesienia do tymczasowego obiektu jest ważne w C++odniesienie const do tymczasowego obiektu zostanie uszkodzony po zakresu funkcji (czas życia)

int main() 
{ 
    int a = 21; 
    int b = 21; 

    //error: invalid initialization of non-const reference 
    //int  & sum = a + b;e [...] 

    //OK 
    int const & sum = a + b; 

    return sum; 
} 

Ale w poniższym przykładzie const odniesienie refnop odnosi się do zniszczonego obiektu tymczasowego. Zastanawiam się dlaczego?

#include <string> 
#include <map> 

struct A 
{ 
    // data 
    std::map <std::string, std::string> m; 
    // functions 
    const A& nothing()   const { return *this; } 
    void init()      { m["aa"] = "bb"; } 
    bool operator!= (A const& a) const { return a.m != m; } 
}; 

int main() 
{ 
    A a; 
    a.init(); 

    A const& ref = A(a); 
    A const& refnop = A(a).nothing(); 

    int ret = 0; 
    if (a != ref)  ret += 2; 
    if (a != refnop) ret += 4; 

    return ret; 
} 

Testowany przy użyciu GCC 4.1.2 i MSVC 2010, zwraca 4;

$> g++ -g refnop.cpp 
$> ./a.out ; echo $? 
4 

Różnica między ref i refnop jest wezwanie do nothing() które tak naprawdę nic. Wygląda na to, że po tym wezwaniu obiekt tymczasowy zostanie zniszczony!

Moje pytanie:
Dlaczego w przypadku refnop, czas życia tymczasowego obiektu nie jest taka sama jak jego const odniesienia?

+0

Uwaga: za pomocą g ++ wersje 4.4 i 4.6, ten fragment zwraca 0 ... – olibre

Odpowiedz

9

Przedłużenie żywotności obiektu tymczasowego można wykonać tylko raz, gdy obiekt tymczasowy zostanie przywiązany do pierwszego odniesienia. Po tym czasie wiedza, że ​​odwołanie odnosi się do obiektu tymczasowego, zniknęła, więc dalsze przedłużenie okresu istnienia nie jest możliwe.

Sprawa, która jest zastanawiające Ci

A const& refnop = A(a).nothing(); 

jest podobna do tej sprawy:

A const& foo(A const& bar) 
{ 
    return bar; 
} 
//... 
A const& broken = foo(A()); 

W obu przypadkach, tymczasowy zostanie zobowiązany do argumentu funkcji (implicite this dla nothing(), bar dla foo()), a jego żywotność jest "rozszerzona" na czas trwania argumentu funkcji. Dodałem "rozszerzone" w cudzysłowie, ponieważ naturalny czas trwania tymczasowego jest już dłuższy, więc nie ma faktycznego przedłużenia.

Ponieważ właściwość przedłużenia żywotności jest nieprzechodnia, zwrot referencji (który odnosi się do obiektu tymczasowego) nie spowoduje przedłużenia czasu życia obiektu tymczasowego, w wyniku czego zarówno refnop, jak i zakończy się odnosząc się do obiekty, które już nie istnieją.

1

Mój oryginalny przykład jest złożony.

Dlatego zamieszczam tutaj prostszy przykład i dostarczam odpowiedni paragraf ISO C++ standard.

To prostsze przykładem jest również dostępny na coliru.stacked-crooked.com/

#include <iostream> 

struct A 
{ 
    A(int i) { std::cout<<"Cstr "<< i<<'\n'; p = new int(i); } 
~A()  { std::cout<<"Dstr "<<*p<<'\n'; delete p;  } 

    const A& thiz() const { return *this; } 

    int *p; 
}; 

const A& constref(const A& a) 
{ 
    return a; 
} 

int main() 
{ 
    const A& a4 = A(4); 
    const A& a5 = A(5).thiz(); 
    const A& a6 = constref(A(6)); 

    std::cout << "a4 = "<< *a4.p <<'\n'; 
    std::cout << "a5 = "<< *a5.p <<'\n'; 
    std::cout << "a6 = "<< *a6.p <<'\n'; 
} 

Wyjście z użyciem wiersza poleceń g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out:

Cstr 4 
Cstr 5 
Dstr 5 
Cstr 6 
Dstr 6 
a4 = 4 
a5 = 0 
a6 = 0 
Dstr 4 

Jak widać, tymczasowe obiekty odwołuje a5 i a6 są niszczone u koniec funkcji odpowiednio thiz i constref.

Jest to wyciąg z §12.2 obiektów tymczasowych, gdzie pogrubione części stosuje się w tym przypadku:

Drugi kontekst jest, gdy odwołanie jest związany z tymczasowym. czasowy, do których odniesienia jest związany lub tymczasowy że pełna przedmiotem podobiektu do którego odniesienie wiąże utrzymuje się przez cały okres odniesienia, z wyjątkiem:

  • tymczasowa związana z elementem referencyjnym w konstruktorze inicjator ctor (12.6.2) działa dopóki konstruktor nie zakończy pracy.
  • Czasowe powiązanie z parametrem referencyjnym w wywołaniu funkcji (5.2.2) trwa do zakończenia pełnego wyrażenia zawierającego połączenie.
  • Żywotność tymczasowego powiązania z wartością zwróconą w instrukcja zwrotu funkcji (6.6.3) nie jest przedłużona; tymczasowy jest niszczony na końcu pełnego wyrażenia w instrukcji return.
  • tymczasowa związana z wzorcem w nowej-inicjator (5.3.4) utrzymuje do wykonania pełnej ekspresji zawierającego nową-inicjator.

To jest bardziej kompletny przykład:

#include <iostream> 

struct A 
{ 
    A()   { std::cout<<"Cstr 9\n";   p = new int(v = 9);  } 
    A(int i) { std::cout<<"Cstr "<<i<<'\n'; p = new int(v = i);  } 
    A(const A&o){ std::cout<<"Copy "<<o.v<<'\n'; p = new int(v = 10+o.v); } 
    ~A()   { std::cout<<"Del "<<v<<' '<<*p<<'\n'; *p = 88; delete p; } 

    const A& thiz() const { return *this; } 

    int *p; 
    int v; 
}; 

const A& constref(const A& a) 
{ 
    return a; 
} 

std::ostream& operator<<(std::ostream& os, const A& a) 
{ 
    os <<"{ *p="<< *a.p <<" , v="<< a.v <<" }\n"; 
    return os; 
} 

int main() 
{ 
    std::cout << "---const A a1 = A(1)"    "\n"; 
        const A a1 = A(1); 
    std::cout << "---const A a2 = A(2).thiz()"   "\n"; 
        const A a2 = A(2).thiz(); 
    std::cout << "---const A a3 = constref(A(3))" "\n"; 
        const A a3 = constref(A(3)); 
    std::cout << "---const A& a4 = A(4)"    "\n"; 
        const A& a4 = A(4); 
    std::cout << "---const A& a5 = A(5).thiz()"   "\n"; 
        const A& a5 = A(5).thiz(); 
    std::cout << "---const A& a6 = constref(A(6))" "\n"; 
        const A& a6 = constref(A(6)); 

    std::cout << "a1 = "<< a1; 
    std::cout << "a2 = "<< a2; 
    std::cout << "a3 = "<< a3; 
    std::cout << "a4 = "<< a4; 
    std::cout << "a5 = "<< a5; 
    std::cout << "a6 = "<< a6; 
} 

I odpowiednie wyjście z użyciem tej samej linii poleceń g++:

---const A a1 = A(1) 
Cstr 1 
---const A a2 = A(2).thiz() 
Cstr 2 
Copy 2 
Del 2 2 
---const A a3 = constref(A(3)) 
Cstr 3 
Copy 3 
Del 3 3 
---const A& a4 = A(4) 
Cstr 4 
---const A& a5 = A(5).thiz() 
Cstr 5 
Del 5 5 
---const A& a6 = constref(A(6)) 
Cstr 6 
Del 6 6 
a1 = { *p=1 , v=1 } 
a2 = { *p=12 , v=12 } 
a3 = { *p=13 , v=13 } 
a4 = { *p=4 , v=4 } 
a5 = { *p=0 , v=5 } 
a6 = { *p=0 , v=6 } 
Del 4 4 
Del 13 13 
Del 12 12 
Del 1 1