2015-09-03 19 views
7

ho trovato strano comportamento di un codice che apparentemente ignorando const-ness:restituendo l'oggetto di costante e assegnandolo alla non costante oggetto

#include <iostream> 

using std::cerr; 

class A 
{ 
public: 
    A() { cerr << "A::A()\n"; } 
    A(const A &a) { cerr << "A::A(const A&)\n"; } 
    A(A &&) { cerr << "A::A(A&&)\n"; } 
    A & operator = (const A &a) { cerr << "A::operator=(const A&)\n"; return *this; } 
    A & operator = (A &&a) { cerr << "A::operator(A&&)\n"; return *this; } 
    ~A() { cerr << "A::~A()\n"; } 

    const A get() const { cerr << "const A A::get() const\n"; return A(); } 
    A get() { cerr << "A A::get()\n"; return A(); } 
}; 

int main() 
{ 
    const A a; 
    A b = a.get(); 
} 

In primo luogo, quello che mi aspettavo qui: a è una costante , quindi viene invocata la versione costante di get(). Successivamente, viene restituito l'oggetto costante, ma sul lato sinistro è l'oggetto non costante b, quindi è necessario chiamare il costruttore di copia. Quale non è:

A::A() 
const A A::get() const 
A::A() 
A::~A() 
A::~A() 

Questo comportamento è previsto dallo standard C++? Quindi, è giusto che la costanza di un oggetto temporaneo venga semplicemente ignorata da RVO? E come si potrebbe applicare la copia qui?

uscita con copia-elision disabilitata (-fno-elide-constructors) fa una mossa ulteriore e la chiamata costruttore di copia atteso:

A::A() 
const A A::light_copy() const 
A::A() 
A::A(A&&) 
A::~A() 
A::A(const A&) 
A::~A() 
A::~A() 
A::~A() 

Se a oggetto non è costante, allora sarà due mosse senza copiare, che è previsto anche.

PS. Il comportamento è importante per me perché quello che vedo è la violazione della rigidità di copia superficiale: per la versione const di get() (che è in realtà shallow_copy()), devo essere sicuro che non verrà apportata alcuna modifica dell'oggetto restituito, perché il reso restituisce l'oggetto è una copia superficiale e una modifica sulla copia superficiale influenzerà l'oggetto "genitore" (che potrebbe essere una costante).

+2

ottimizzazione Restituire il valore (RVO) in azione. – Jarod42

+0

BTW, vedi [dovrebbe-i-ancora-return-const-objects-in-c11] (http://stackoverflow.com/questions/13099942/should-i-still-return-const-objects-in-c11) – Jarod42

+0

Con "light_copy", intendi "copia superficiale"? –

risposta

7

So, is it okay that constness of a temporary object is simply ignored by RVO?

Sì. [Class.copy]/P31 (citando N4527, che incorpora alcuni DR che chiarisce l'intento; sottolineatura mia):

This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

  • in a return statement in a function with a class return type, when the expression is the name of a nonvolatile automatic object (other than a function parameter or a variable introduced by the exception-declaration of a handler (15.3)) with the same type (ignoring cv-qualification) as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value
  • [...]
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same type (ignoring cv-qualification), the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • [...]

Il terzo proiettile è quello applicabile qui; si noti che una regola simile si applica anche al NRVO (primo punto).

+0

Grazie per il chiarimento; qualche idea su come si possa applicare la copia qui? –

+0

@AlexanderSergeev Che cosa esattamente speri di ottenere copiando qui?Le tue funzioni restituiscono * nuovi * oggetti; se imposti la copia, copierai un nuovo oggetto in un altro nuovo oggetto, quindi getterai via il primo oggetto. La ragione per cui lo standard consente di copiare elision è evitare di creare e scartare oggetti superflui; non dovrebbe avere alcun effetto sul comportamento effettivo del codice. –

+0

@KyleStrand Tutto ciò è effettivamente necessario per implementare 'shallow_copy': se l'oggetto genitore è costante, ho bisogno di assicurarmi che il const-overload di' shallow_copy' restituirà un oggetto costante. E il copy-constructor crea una copia profonda. Quindi, il punto è che 'const Un genitore; const A copy = parent.shallow_copy(); 'funziona come previsto, e' const Un genitore; Una copia = parent.shallow_copy(); 'utilizza un costruttore di copie aggiuntivo (poiché il tipo restituito è costante e il lato sinistro non lo è) e quindi esegue una copia profonda. Ma non funziona perché NVRO ignora la restrizione di const'ness. –

1

Se si vuole proibire la costruzione/assegnazione da const temporanea, si può contrassegnare come cancellato questi metodi:

A(const A &&) = delete; 
A& operator = (const A &&) = delete; 

Live Demo

+0

Ho ricevuto un errore: 'uso della funzione cancellata 'A :: A (A &&)'' quando l'operatore di spostamento-costruttore/assegnazione viene cancellato (gcc-4.9.3) –

+0

Ho aggiunto un 'const' e funziona come previsto per oggetto non const: [Demo] (https://ideone.com/K9t5Gt). – Jarod42

+0

Ok, ma voglio essere in grado di lavorare con oggetti costanti o –