2015-10-22 9 views
6

Sto prendendo una lezione sulla programmazione orientata agli oggetti usando C++.C++ compilatore 'superficiale' copie e assegnazioni

Nel nostro testo si dice,

Se non ci dichiariamo un costruttore di copia, il codice compilatore inserisce che implementa una copia. Se non dichiariamo un incarico con l'operatore , il compilatore inserisce il codice che implementa un assegnamento superficiale .

Quello che voglio sapere, è se questo è in realtà vero, ciò che l'allusione al meccanismo del compilatore viene effettivamente chiamato, e come funziona.

Questo è non una domanda sui costruttori di copia, riguarda il comportamento del compilatore.

MODIFICA> Maggiori contesto

Copia Constructor come definito dal testo:

La definizione di un costruttore di copia contiene la logica che

  1. esegue una copia su tutte le variabili di istanza non risorsa
  2. al individua memoria per ogni nuova risorsa
  3. copia i dati dalla risorsa di origine (s) alla risorsa appena creata (s)

Resource come definito dal testo

memoria che un oggetto che alloca in fase di esecuzione rappresenta una risorsa di quella classe dell'oggetto .

La gestione di questa risorsa richiede una logica aggiuntiva non necessaria per le classi più semplici che non accedono alle risorse. Questa logica aggiuntiva garantisce la corretta gestione della risorsa ed è spesso denominata copia profonda e assegnazione .

+2

Senza alcun contesto, la citazione suona nel migliore dei casi. – juanchopanza

+1

In base a "https://en.wikipedia.org/wiki/Object_copying", la copia superficiale viene utilizzata correttamente (concordato, potrebbe confondere i programmatori C++) –

+0

L'istruzione è VERO. – user3344003

risposta

7

E 'più esatto dire che il compilatore definisce un costruttore copia predefinito e un predefinita copia operatore di assegnazione. Questi copieranno/costruiranno il nuovo oggetto semplicemente chiamando il costruttore di copie per tutte le variabili membro.

  • Per primitive come int s e float s, questo di solito non è un problema.
  • Per i puntatori, però. Questa è una brutta notizia! Cosa succede quando il primo oggetto cancella quel puntatore? Ora il puntatore dell'altro oggetto non è valido!
  • Se una variabile membro non può essere copiata (forse è stato utilizzato un std::unique_ptr per risolvere il problema precedente), l'assegnazione/ctor di copia predefinita non funzionerà. Come puoi copiare qualcosa che non può essere copiato? Ciò porterà a un errore del compilatore.

Se si definisce il proprio operatore di copia/costruttore di copia, è possibile effettuare una "copia profonda". È possibile:

  • creare un nuovo oggetto, piuttosto che copiare il puntatore
  • esplicitamente "copia superficiale" un puntatore
  • due precedenti in base a quello che realmente vuole Mix!
  • Inizializza le variabili membro con valori predefiniti/personalizzati negli oggetti copiati anziché copiare ciò che era nell'oggetto originale.
  • Disabilita la copia del tutto
  • On e avanti e avanti

Come si può vedere, ci sono un sacco di motivi per cui ci si vuole implementare (o esplicitamente vietare) il proprio operatore di assegnamento per copia, copia costruttore , le loro controparti di movimento e distruttore. Infatti, esiste un noto idioma C++ noto come The Rule of Five (in precedenza la regola del 3) che può guidare la tua decisione su quando farlo.

+4

Un sacco di motivi? Quale? Se ti ritrovi a implementare le funzioni membro speciali, è probabile che tu abbia commesso un errore di progettazione da qualche parte. – juanchopanza

+0

* quindi l'assegnamento/ctor predefinito fallirà * non il default sarà esplicitamente contrassegnato come 'delete' se la classe non è copiabile. http://coliru.stacked-crooked.com/a/40da9a87d79923e9 – NathanOliver

+0

Vedere anche la [Regola zero] (https://rmf.io/cxx11/rule-of-zero/). –

7

Sì, è vero, ed è effettivamente chiamato copia superficiale. Per quanto riguarda il modo in cui funziona, diciamo che hai una variabile puntatore e la assegni a un'altra variabile puntatore. Questo copia solo il puntatore e non quello a cui punta, questa è una copia poco profonda. Una copia profonda di avrebbe creato un nuovo puntatore e copiato il contenuto effettivo a cui punta il primo puntatore.

Qualcosa di simile a questo:

int* a = new int[10]; 

// Shallow copying 
int* b = a; // Only copies the pointer a, not what it points to 

// Deep copying 
int* c = new int[10]; 
std::copy(a, a + 10, c); // Copies the contents pointed to by a 

Il problema con la copia poco profonda per quanto riguarda i puntatori dovrebbe essere abbastanza ovvio: Dopo l'inizializzazione del b nell'esempio di cui sopra, si ha due puntatori sia che punta alla stessa memoria Se uno allora fa delete[] a; allora entrambi i puntatori diventano non validi. Se i due puntatori si trovano in oggetti diversi di una classe, non esiste una vera connessione tra i puntatori e il secondo oggetto non saprà se il primo oggetto ha cancellato la sua memoria.

+1

@hyde La classe 'std :: string' (così come altre classi contenitore come' std :: vector') hanno implementato gli operatori di copia-costruzione e assegnazione per eseguire la copia profonda. –

+0

Capisco cosa stai dicendo, ed è una risposta molto chiara ... ma non sono ancora sicuro di quale codice il compilatore sta 'inserendo'. – bigcodeszzer

+1

@bigcodeszzer Leggi il [implicitamente costruttore dichiarato in modo predefinito] (http://en.cppreference.com/w/cpp/language/default_constructor#Implicitly-declared_default_constructor), nonché il [implicitamente dichiarato costruttore di copie] (http://en.cppreference.com/w/cpp/language/copy_constructor#Implicitly-declared_copy_constructor) e [operatore di assegnazione copia dichiarato implicitamente] (http://en.cppreference.com/w/cpp/language/as_operator# implicitamente-declared_copy_assignment_operator). –

2

Il codice per copia superficiale è un compito semplice per ogni campo. Se:

class S { 
    T f; 
}; 
S s1, s2; 

un'assegnazione come s1=s2; è equivalente a quanto avviene nel seguente:

class S { 
    T f; 
    public: 
    S &operator=(const S&s) { 
     this->f = s.f; // and such for every field, whatever T is 
    } 
}; 
S s1, s2; 
s1=s2; 

Questo è indicato nel 12.8-8 del progetto di norma:

Il costruttore di copia implicitamente dichiarata per una classe X avrà la modulo X :: X (const X &) se

- ciascuna classe base diretta o virtuale B di X ha un costruttore di copia cui primo parametro è di tipo const B & o const volatili B & e

- per tutti i membri dati non statici di X che sono di un tipo di classe M (o serie di essi), ciascuno tale tipo di classe ha un costruttore di copia il cui primo parametro è di tipo const M & o const volatili M & .123

In caso contrario, il costruttore di copia implicitamente dichiarata avrà la forma X :: X (X &)

12.8-28 dice:

La copia/spostamento operatore di assegnazione implicitamente definito per un non-union classe X esegue memb erwise copia/sposta l'assegnazione dei suoi sottooggetti. [...] nell'ordine in cui sono stati dichiarati nella definizione della classe.

+0

Quindi, quale codice inserisce il compilatore nel caso di un costruttore predefinito? – bigcodeszzer

0

userò una classe base per definire il comportamento del compilatore come meglio so come.

La versione di questa classe con la sua dichiarazione di default costruirà sia un costruttore di copie che un operatore uguale.

class Student sealed { 
private: 
    std::string m_strFirstName; 
    std::string m_strLastName; 

    std::vector<unsigned short> m_vClassNumbers; 
    std::vector<std::string> m_vTeachers; 

    std::vector<unsigned short> m_vClassGrades; 

public: 
    Student(const std::string& strFirstName, const std::string& strLastName); 

    std::string getFirstName() const; 
    std::string getLastName() const; 

    void setClassRoster(std::vector<unsigned short>& vClassNumbers); 
    std::vector<unsigned short>& getClassRoster() const; 

    void setClassTeachers(std::vector<std::string>& vTeachers); 
    std::vector<std::string>& getClassTeachers() const; 

    void setClassGrades(std::vector<unsigned short>& vGrades); 
    std::vector<unsigned short>& getGrades() const;  

private: 
    // These Are Not Commented Out But Are Defined In The Private Section 
    // These Are Not Accessible So The Compiler Will No Define Them 
    Student(const Student& c); // Not Implemented 
    Student& operator=(const Student& c); // Not Implemented 
}; 

Dove la seconda versione di questa classe non sarà in quanto ho dichiarato entrambi come privati!

Questo è probabilmente il modo migliore per dimostrarlo. Ho mostrato l'interfaccia del file di intestazione solo a questa classe poiché il codice sorgente o il codice C++ da compilare non è un problema. La differenza nel modo in cui questi due sono definiti durante la fase di pre-compilazione impone come funzionerà il compilatore prima che inizi a compilare il codice sorgente nel codice oggetto.

Tenere presente tuttavia che i contenitori di librerie standard & implementano i propri operatori di assegnazione di copia &. Ma lo stesso concetto si applica al comportamento del compilatore se una classe ha tipi di base come int, float, double, ecc. Quindi il compilatore tratterà una classe Simple nello stesso modo in base alla sua dichiarazione.

class Foo { 
private: 
    int m_idx; 
    float m_fValue; 

public: 
    explicit Foo(float fValue); 

    // Foo(const Foo& c); // Default Copy Constructor 
    // Foo& operator=(const Foo& c); // Default Assignment Operator 
}; 

seconda versione

class Foo { 
private: 
    int m_idx; 
    float m_fValue; 

public: 
    explicit Foo(float fValue); 

private: 
    Foo(const Foo& c); // Not Implemented 
    Foo& operator=(const Foo& c); // Not Implemented 
}; 

Il compilatore tratterà questa classe nello stesso modo; non definirà nessuno di questi poiché non saranno implementati a causa della sua dichiarazione di privato.