Quando viene chiamata una funzione, la funzione deve restituire un valore. Quel valore ha bisogno di memoria per vivere, ma il valore di ritorno deve sopravvivere alla funzione stessa. L'ABI definisce come funziona tutto questo. In generale, ciò avviene quando il chiamante fornisce un pezzo di memoria della dimensione/allineamento per il valore restituito alla funzione.
Quindi, se una funzione calcola un valore e lo restituisce, deve (in teoria) copiare quel valore calcolato nella memoria del valore di ritorno. E quando il chiamante lo recupera, deve (in teoria) copiare la memoria del valore di ritorno in qualche altro oggetto stack per un uso successivo.
L'elisione di copia non garantita dice che nessuna di queste copie è necessaria. Sul lato della funzione di ritorno, al compilatore è permesso utilizzare semplicemente la memoria del valore di ritorno internamente quando genera quel valore, quindi l'istruzione return non deve copiare nulla. E sul lato ricevente, se la memoria verrà utilizzata per inizializzare un oggetto stack, non dovrà copiare in quella memoria.
L'elisione di copia garantita dice che se il lato ricevente sta inizializzando un oggetto dello stesso tipo, il ricevitore non considererà se l'oggetto ha un costruttore copia/sposta. Pertanto, il codice che chiama una funzione come auto t = Func();
non lo considererà come una potenziale operazione di copia in t
. Il compilatore che elabora il codice chiamerà Func
con la memoria del valore di ritorno che si trova nello spazio di stack per t
.
E sul lato chiamato, se si restituisce direttamente un valore nominale, non è necessario che esista un costruttore copia/sposta. Il callee costruirà il valore nominale direttamente nella memoria del valore di ritorno.
Ecco la cosa: agli ABI non importa nulla di tutto ciò. Tutto ciò a cui ABI si preoccupa è la memoria di basso livello. Cioè, fintanto che il chiamante sta passando la memoria del valore di ritorno della dimensione e dell'allineamento appropriate e il callee sta inizializzando quella memoria con un oggetto del tipo appropriato ... l'ABI non si cura di.
Se il chiamante desidera utilizzare la memoria del valore di ritorno per operazioni successive, ciò va bene per l'ABI. Se il destinatario desidera inizializzare i dati direttamente nella memoria del valore di ritorno invece di copiarlo, l'ABI non se ne accorgerà.
L'ABI definisce l'interfaccia; quello che fai con questa interfaccia dipende da te.
Ad esempio, considerare Itanium ABI on return values. Permette di memorizzare i tipi di classe nei registri, ma solo se dispongono di semplici costruttori di copia/spostamento. In caso contrario, indipendentemente dal loro contenuto, devono essere costruiti nella memoria fornita dalla funzione chiamante. Se la classe è banalmente riproducibile, non è possibile distinguere tra elisione e non elisione.
L'unico modo in cui un ABI può porre un problema a questa funzione è se l'ABI ha deciso arbitrariamente dove il valore di ritorno (e presumibilmente i parametri) è memorizzato, l'uno rispetto all'altro. Cioè, l'ABI costringe il chiamante a posizionare l'oggetto sullo stack in una posizione specifica rispetto ai parametri.
Potrebbe esistere un ABI simile? Non ho alcuna conoscenza particolare per dire che non può. Lo fa? Ne dubito piuttosto, dato che un tale ABI renderebbe difficile l'elisione in generale.
Posso immaginare un ABI (se ridicolo) che sarebbe rotto da questo cambiamento. Sarebbe sufficiente o stai cercando istanze reali di un ABI effettivamente utilizzato da un compilatore non giocattolo che si rompe? – Yakk
Come si può rompere qualcosa che non esiste? – user3104201
"* Sto pensando a cose come le inizializzazioni che elidono le copie quando la creazione di un oggetto può essere sottolineata, ma non quando incrocio i confini delle unità di compilazione. *" Non conosco tali circostanze in cui ciò sarebbe possibile. –