2016-03-03 11 views
12

Questa sembra una domanda di base, ma non ho trovato alcuna risposta esaustiva, quindi eccoci qui. Considerate questo frammento di codice:Memorizzare il riferimento const a un oggetto nella classe

struct A { 
    const std::string& s; 
    A(const std::string& s) : s(s) {} 
}; 

int main() { 
    A a("abc"); 
    std::cout << a.s << std::endl; 
    return 0; 
} 

Demo.

Finché ho capito, questo è UB. Il letterale stringa "abc" si lega a const std::string& nel costruttore, creando un oggetto stringa temporaneo. È inoltre obbligatorio fare riferimento allo a.s e viene distrutto una volta creato a. Cioè, il riferimento const non può concatenare il prolungamento della durata. Riferimento penzolante, boom. In questo caso particolare, non vedo alcun output su ideone.com, ma qualsiasi cosa potrebbe accadere (ricorda velociraptors).

Ok, questo è chiaro. Ma cosa succede se questo è in realtà il nostro vero intento: vogliamo memorizzare un riferimento const a un oggetto? Ad uno esistente, non temporaneo? Questo sembra un compito molto naturale, ma ho trovato solo una (quasi) soluzione naturale. Accettare l'argomento di costruttore da std::reference_wrapper invece che con riferimento:

A(std::reference_wrapper<const std::string> r) : s(r) {} 

Dal std::reference_wrapper ha cancellato costruttori da provvisori:

reference_wrapper(T&& x) = delete; 

questo funziona come previsto. Tuttavia, questo non è abbastanza elegante. Un altro approccio a cui posso pensare è accettare il riferimento di inoltro T&& e rifiutare tutto tranne le stringhe di valore const con std::enable_if. Questo è ancora meno elegante, penso.

Eventuali altri approcci?

UPD Un'altra domanda: si tratta di un uso legittimo di std::reference_wrapper o può essere considerato troppo specifico?

+1

Qualsiasi motivo per cui 'A' non può avere solo un normale' std :: string'? 'A :: s' è probabile che penzoli anche se riceve un lvalue e il riferimento interrompe l'operatore di assegnazione. Questo ha un caso d'uso o è puramente accademico? – nwp

+3

'A (const std :: string &&) = delete;' potrebbe fare quello che vuoi. Non penso ancora che sia una buona idea. – nwp

+0

@nwp Questo potrebbe essere utile nelle classi di utilità, dove controlliamo la durata delle sue istanze e possiamo garantire che non sopravviveranno all'oggetto passato. Ho usato 'std :: string' per denotare un costoso oggetto da copiare. Una soluzione alternativa, più sicura, sarebbe quella di memorizzare 'shared_ptr' su questo oggetto. Tuttavia, questo potrebbe essere eccessivo in scenari semplici. – Mikhail

risposta

4

direi che la soluzione naturale sarebbe quella di fare ciò che fa reference_wrapper: impedire la costruzione da provvisori:

struct A { 
    const std::string& s; 
    A(const std::string& s) : s(s) {} 
    A(std::string&&) = delete; 
}; 

Si dovrebbe anche tenere a mente che avere un membro di dati di tipo di riferimento rende la classe non assegnabile (non è possibile nemmeno l'assegnazione dello spostamento) per impostazione predefinita ed è generalmente difficile implementare un operatore di assegnazione. Si dovrebbe considerare di memorizzare un puntatore invece di un riferimento:

+1

Buona risposta. L'unico svantaggio è che se abbiamo più argomenti di questo tipo, sarebbe abbastanza difficile eliminare tutti i sapori indesiderati. – Mikhail

+1

Non mi piace l'approccio puntatore - non ha informazioni sintattiche che non possa essere nullptr. – Mikhail

+0

@Mikhail Considero un oggetto imperfetto, ma utilizzabile, superioir a puro, ma inutilizzabile ;-) Tuttavia, è possibile utilizzare qualcosa come 'not_null ' dalla libreria [Core Guideline] (https://github.com/isocpp) /CppCoreGuidelines/blob/master/CppCoreGuidelines.md) – Angew

Problemi correlati