2010-06-22 17 views
7

Sappiamo tutti che le cose di questo tipo sono validi in C++:const riferimento alla stranezza temporanea

const T &x = T(); 

mentre:

T &x = T(); 

non è.

In a recent question la conversazione porta a questa regola. L'OP aveva pubblicato un codice che evoca chiaramente UB. Ma avrei aspettare una versione modificata di farlo funzionare (Questa è la versione modificata):

#include <iostream> 
using namespace std; 

class A { 
public: 
    A(int k) { _k = k; }; 
    int get() const { return _k; }; 
    int _k; 
}; 

class B { 
public: 
    B(const A& a) : _a(a) {} 
    void b() { cout << _a.get(); } 
    const A& _a; 
}; 

B* f() { 
    return new B(A(10)); 
} 

int main() { 
    f()->b(); 
} 

Questa stampa materiale privo di su alcune macchine, 10 sugli altri ... suona come UB per me :-). Ma poi ho pensato, bene A è fondamentalmente un glorificato int tutto ciò che inizializza uno e leggerlo. Perché non basta chiamare un Aint e vedere cosa succede:

#include <iostream> 
using namespace std; 

typedef int A; 

class B { 
public: 
    B(const A& a) : _a(a) {} 
    void b() { cout << _a; } 
    const A& _a; 
}; 

B* f() { 
    return new B(A(10)); 
} 

int main() { 
    f()->b(); 
} 

esso stampa 10 ogni volta. Almeno sembra come la regola di riferimento const è valida per la versione int, ma non per la versione di classe. Sono entrambi semplicemente UB a causa dell'uso dell'heap? Sono solo fortunato con la versione int perché la compilazione ha visto tutti gli const s e ha appena stampato direttamente uno 10? Quale aspetto della regola mi manca?

risposta

15

Semplicemente dimostra che l'analisi del comportamento del linguaggio "provandolo nel compilatore" normalmente non produce risultati utili. Entrambi i tuoi esempi non sono validi per lo stesso motivo.

La durata del temporaneo viene estesa solo quando si utilizza tale temporizzatore come inizializzatore diretto per un riferimento const, solo per stabilire un collegamento "a vita" tra il riferimento e il temporaneo.

Provare a passare un temporaneo come argomento del costruttore e collegare un riferimento const all'interno del costruttore non stabilirà il collegamento sopra menzionato e non prolungherà la durata del temporaneo.

Inoltre, in conformità con lo standard C++, se si esegue questa

struct S { 
    const int &r; 

    S() : r(5) { 
    cout << r; // OK 
    } 
}; 

la durata della temporanea è valida solo fino alla fine del costruttore. Una volta che il costruttore è finito, il temporaneo muore, il che significa che questo

S s; 
cout << s.r; // Invalid 

non è valido.

L'esperimento con int semplicemente "sembra funzionare", per puro caso.

+0

Ti suggerisco di cambiare "un riferimento const" a "un riferimento const * locale * per rendere le cose ancora più chiare;) – fredoverflow

+0

@FredOverflow: Perché stai insistendo su un riferimento * local *? Puoi dichiarare legalmente 'const std :: string & r =" Hello "' in ambito namespace e ottenere la stessa estensione lifetime. – AnT

+0

@AndreyT, come viene stabilito questo * collegamento * a quale livello? –

3

Sei solo fortunato. Modifica B :: b in questo:

void b() { 
    int i = rand(); 
    int j = rand(); 
    cout << _a << endl; 
} 

stampa numeri casuali.

+0

Whoops, post errato modificato, rollback :) – fredoverflow

3

Stampa 10 ogni volta.

Modificare la funzione principale un po 'e non lo farà stampare 10 più:

int main() 
{ 
    B* p = f(); 
    cout << "C++\n"; // prints C++ 
    p->b();   // prints 4077568 
} 

come si fa questo link ottiene stabilito a quale livello?

vedi 12.2 [class.temporary] §4 e §5:

oggetti temporanei vengono distrutti come ultimo passaggio nella valutazione pieno-espressione (lessicale) contiene il punto in cui sono stati creati .

Esistono due contesti in cui i provvisori vengono distrutti in un punto diverso rispetto alla fine dell'espressione completa. Il primo contesto è [...]

Il secondo contesto è quando un riferimento è associato a un temporaneo. Il temporaneo al quale il riferimento è vincolato o il temporaneo che è l'oggetto completo di un suboggetti a cui il riferimento è associato persiste per la durata del riferimento eccetto: [...]

Un vincolo temporaneo a un parametro di riferimento in una chiamata di funzione persiste fino al completamento dell'espressione completa che contiene la chiamata.

Quindi nel tuo caso, il temporaneo viene distrutto dopo la valutazione della piena espressione new B(A(10)).

Problemi correlati