2012-07-15 7 views
14

Stavo ricevendo uno strano errore da gcc e non riesco a capire perché. Ho creato il seguente codice di esempio per rendere più chiaro il problema. Fondamentalmente, c'è una classe definita, per la quale io faccio il suo costruttore di copia e l'operatore di assegnazione delle copie privato, per evitare di chiamarli accidentalmente.vector :: push_back insiste sull'uso del costruttore di copie anche se viene fornito un costruttore di spostamenti

#include <vector> 
#include <cstdio> 
using std::vector; 

class branch 
{ 
public: 
    int th; 

private: 
    branch(const branch& other); 
    const branch& operator=(const branch& other); 

public: 

    branch() : th(0) {} 

    branch(branch&& other) 
    { 
    printf("called! other.th=%d\n", other.th); 
    } 

    const branch& operator=(branch&& other) 
    { 
    printf("called! other.th=%d\n", other.th); 
    return (*this); 
    } 

}; 



int main() 
{ 
    vector<branch> v; 
    branch a; 
    v.push_back(std::move(a)); 

    return 0; 
} 

Mi aspetto che questo codice venga compilato, ma non riesce con gcc. In realtà gcc lamenta che "branch :: branch (const ramo &) è privato", che come ho capito non dovrebbe essere chiamato.

L'operatore di assegnazione funziona, dal momento che se sostituisco il corpo di main() con

branch a; 
branch b; 
b = a; 

Sarà compilato ed eseguito come previsto.

È un comportamento corretto di gcc? Se è così, cosa c'è di sbagliato nel codice sopra? Qualsiasi suggerimento mi è utile. Grazie!

+0

Funziona per me con gcc-4.6.1. –

+0

Stavo usando gcc 4.7.1-2. Proverò 4.6.1. Grazie! – BreakDS

+2

Con la mia lettura di N3242, questo codice dovrebbe essere consentito (ma se il costruttore di move lancia un'eccezione, il programma ha un comportamento non definito). – aschepler

risposta

16

Prova ad aggiungere "noexcept" alla dichiarazione del costruttore di spostamenti.

Non posso citare lo standard, ma le versioni recenti di gcc sembrano richiedere che il costruttore di copie sia pubblico o che il costruttore di spostamenti sia dichiarato "noexcept". Indipendentemente dal qualificatore "noexcept", se rendi pubblico il costruttore di copie, si comporterà come ci si aspetta in fase di esecuzione.

+3

Se rende pubblico il costruttore di copie, l'oggetto verrà copiato, che è chiaramente quello che sta cercando di evitare. In ogni caso, fare in modo che il costruttore del movimento 'noexcept' sia la soluzione corretta qui, quindi +1. – ildjarn

+0

Grazie ad entrambi, noxcept e public copy constructor sono entrambi corretti. Tuttavia, è un po 'contro-intuitivo che il costruttore di copie non possa essere reso privato. – BreakDS

+0

@BreakDS: il costruttore di copia può essere reso privato (assumendo un'implementazione di libreria standard corretta) se il costruttore di movimento è 'noexcept'. – ildjarn

9

Diversamente da quanto suggerito dalla risposta precedente, gcc 4.7 era errato per rifiutare questo codice, un errore che è stato corrected in gcc 4.8.

La piena comportamento standard conforme per vector<T>::push_back è:

  • Se c'è solo un costruttore di copia e nessuna mossa costruttore, push_back copierà il suo argomento e darà la garanzia forte sicurezza rispetto alle eccezioni. Cioè, se il push_back fallisce a causa di un'eccezione innescata dalla riallocazione della memoria vettoriale, il vettore originale rimarrà invariato e utilizzabile. Questo è il comportamento noto di C++ 98 ed è anche il motivo del disordine che segue.
  • Se c'è un costruttore noexcept mossa per T, push_back sarà spostare dal suo argomento e darà la garanzia forte eccezione. Nessuna sorpresa qui.
  • Se c'è un costruttore mossa che è nonnoexcept e c'è anche un costruttore di copia, push_back sarà copia l'oggetto e dare la garanzia forte sicurezza rispetto alle eccezioni. Questo è inaspettato a prima vista. Mentre push_back potrebbe spostarsi qui, ciò sarebbe possibile solo a scapito del sacrificio della forte garanzia di eccezione. Se hai trasferito il codice da C++ 98 a C++ 11 e il tuo tipo è mobile, questo cambierebbe silenziosamente il comportamento delle chiamate esistenti push_back. Per evitare questa trappola e mantenere la compatibilità con il codice C++ 98, C++ 11 torna alla copia più lenta. Questo è il comportamento di gcc 4.7. Ma c'è di più ...
  • Se c'è un costruttore mossa che non è noexcept ma nessun costruttore di copia a tutti - vale a dire, l'elemento può essere spostato e non copiato - push_back si esibirà il movimento, ma si non dare la garanzia forte sicurezza rispetto alle eccezioni. Qui è dove gcc 4.7 è andato storto. In C++ 98 non ci sono push_back s per i tipi che sono mobili ma non copiabili. Quindi sacrificare la forte sicurezza delle eccezioni qui non infrange il codice esistente. Questo è il motivo per cui è consentito e il codice originale è in realtà legale C++ 11.

Vedere cppreference.com su push_back:

Se viene generata un'eccezione, questa funzione non ha alcun effetto (forte garanzia eccezione).

Se il costruttore di movimento di T non è nient'affatto e il costruttore di copia non è accessibile, il vettore utilizzerà il costruttore di lancio . Se lancia, la garanzia è rinunciata e gli effetti sono non specificati.

O un po '§23.3.6.5 più contorto dal C++ 11 standard (il corsivo è da me):

cause riallocazione se la nuova dimensione è superiore alla vecchia capienza. Se non si verifica alcuna riallocazione, tutti gli iteratori e i riferimenti precedenti allo punto di inserimento rimangono validi. Se viene generata un'eccezione diversa da dal costruttore di copie, dal costruttore di movimento, dall'operatore di assegnazione o dall'operatore di spostamento di o da qualsiasi operazione di InputIterator non vi sono effetti. Se viene generata un'eccezione dal costruttore di spostamenti di una T non-CopyInsertable , gli effetti non sono specificati.

Oppure, se non ti piace leggere, Scott Meyer's Going Native 2013 talk (a partire dalle 00:30:20 con la parte interessante a circa 0:42:00).