Va bene restituire il valore dell'argomento predefinito tramite riferimento const?


26

Va bene restituire il valore dell'argomento predefinito per riferimento const come negli esempi seguenti:

https://coliru.stacked-crooked.com/a/ff76e060a007723b

#include <string>

const std::string& foo(const std::string& s = std::string(""))
{
    return s;
}

int main()
{
    const std::string& s1 = foo();
    std::string s2 = foo();

    const std::string& s3 = foo("s");
    std::string s4 = foo("s");
}

3
Test semplice: sostituisci std::stringcon una tua classe in modo da poter tracciare la costruzione e la distruzione.
user4581301

1
@ user4581301 Se la sequenza è corretta, ciò non proverebbe che il costrutto sia ok.
Peter - Ripristina Monica il

6
@ user4581301 "Sembrava funzionare quando l'ho provato" è la cosa peggiore in assoluto per un comportamento indefinito
HerrJoebob

Va notato che la domanda è un boccone fuorviante nella sua formulazione. Non stai restituendo il valore di un argomento predefinito per riferimento const, ma stai restituendo un riferimento const a un riferimento const (... a un argomento predefinito).
Damon,

2
@HerrJoebob Concorda con l'affermazione al 100%, ma non con il contesto in cui la stai usando. Il modo in cui l'ho letta questa domanda si risolve in "Quando è finita la vita dell'oggetto?" capire quando viene chiamato il distruttore è un buon modo per farlo. Per una variabile automatica il distruttore dovrebbe essere chiamato in tempo o hai grossi problemi.
user4581301

Risposte:


18

Nel tuo codice, entrambi s1e s3sono riferimenti penzolanti. s2e s4sono ok

Nella prima chiamata, l' std::stringoggetto vuoto temporaneo creato dall'argomento predefinito verrà creato nel contesto dell'espressione contenente la chiamata. Pertanto, morirà alla fine della definizione di s1, che lascia s1penzolare.

Nella seconda chiamata, l' std::stringoggetto temporaneo viene utilizzato per inizializzares2 , quindi muore.

Nella terza chiamata, la stringa letterale "s"viene utilizzata per creare un std::stringoggetto temporaneo e che muore anche alla fine della definizione di s3, lasciando s3penzolare.

Nella quarta chiamata, l' std::stringoggetto temporaneo con valore "s"viene utilizzato per inizializzare s4e quindi muore.

Vedi C ++ 17 [class.temporary] /6.1

Un oggetto temporaneo associato a un parametro di riferimento in una chiamata di funzione (8.2.2) persiste fino al completamento dell'espressione completa contenente la chiamata.


1
La parte interessante della risposta è l'affermazione che l'argomento predefinito verrà creato nel contesto del chiamante. Questo sembra essere supportato dalla citazione standard di Guillaume.
Peter - Ripristina Monica il

2
@ Peter-ReinstateMonica Vedi [expr.call] / 4, "... L'inizializzazione e la distruzione di ciascun parametro avviene nel contesto della funzione chiamante. ..."
Brian

8

Non è sicuro :

In generale, la durata di un temporaneo non può essere ulteriormente estesa "passandolo": un secondo riferimento, inizializzato dal riferimento a cui era limitato il temporaneo, non influisce sulla sua durata.


Quindi pensi che std::string s2 = foo();sia valido (dopo tutto, nessun riferimento viene passato esplicitamente)?
Peter - Ripristina Monica il

1
@ Peter-ReinstateMonica che uno è al sicuro poiché verrà costruito un nuovo oggetto. La mia risposta riguarda semplicemente l'espansione della vita. Le altre due risposte hanno già coperto tutto. Non ripeterò nel mio.
Oblivion

5

Dipende da cosa fai dopo con la stringa.

Se la tua domanda è il mio codice è corretto? allora si.

Da [dcl.fct.default] / 2

[ Esempio : la dichiarazione

void point(int = 3, int = 4);

dichiara una funzione che può essere chiamata con zero, uno o due argomenti di tipo int. Può essere chiamato in uno di questi modi:

point(1,2);  point(1);  point();

Le ultime due chiamate sono equivalenti a point(1,4)e point(3,4), rispettivamente. - fine esempio ]

Quindi il tuo codice è effettivamente equivalente a:

const std::string& s1 = foo(std::string(""));
std::string s2 = foo(std::string(""));

Tutto il codice è corretto, ma in nessuno di questi casi non esiste alcuna estensione della durata di riferimento, poiché il tipo restituito è un riferimento.

Poiché si chiama una funzione con un temporaneo, la durata della stringa restituita non prolungherà l'istruzione.

const std::string& s1 = foo(std::string("")); // okay

s1; // not okay, s1 is dead. s1 is the temporary.

Il tuo esempio con s2va bene dato che copi (o sposti) dal temporaneo prima della fine del sazio. s3ha lo stesso problema di s1.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.