Condividi tramite


Classi C++ come tipi valore

Le classi C++ sono, per impostazione predefinita, tipi valore. Possono essere specificati come tipi di riferimento, che consentono il comportamento polimorfico per supportare la programmazione orientata agli oggetti. I tipi valore vengono talvolta visualizzati dal punto di vista del controllo di memoria e layout, mentre i tipi di riferimento riguardano le classi di base e le funzioni virtuali per scopi polimorfici. Per impostazione predefinita, i tipi valore sono copiabili, ovvero esiste sempre un costruttore di copia e un operatore di assegnazione di copia. Per i tipi di riferimento, è possibile rendere la classe non copiabile (disabilitare il costruttore di copia e l'operatore di assegnazione di copia) e usare un distruttore virtuale, che supporta il polimorfismo previsto. I tipi valore riguardano anche il contenuto, che, quando vengono copiati, forniscono sempre due valori indipendenti che possono essere modificati separatamente. I tipi di riferimento riguardano l'identità: qual è il tipo di oggetto? Per questo motivo, i "tipi riferimento" sono detti anche "tipi polimorfici".

Se si vuole davvero un tipo simile a un riferimento (classe base, funzioni virtuali), è necessario disabilitare in modo esplicito la MyRefType copia, come illustrato nella classe nel codice seguente.

// cl /EHsc /nologo /W4

class MyRefType {
private:
    MyRefType & operator=(const MyRefType &);
    MyRefType(const MyRefType &);
public:
    MyRefType () {}
};

int main()
{
    MyRefType Data1, Data2;
    // ...
    Data1 = Data2;
}

La compilazione del codice precedente genererà l'errore seguente:

test.cpp(15) : error C2248: 'MyRefType::operator =' : cannot access private member declared in class 'MyRefType'
        meow.cpp(5) : see declaration of 'MyRefType::operator ='
        meow.cpp(3) : see declaration of 'MyRefType'

Tipi di valore e efficienza di spostamento

L'overhead di allocazione delle copie viene evitato a causa di nuove ottimizzazioni della copia. Ad esempio, quando si inserisce una stringa al centro di un vettore di stringhe, non esiste un sovraccarico di riallocazione della copia, solo uno spostamento, anche se comporta la crescita del vettore stesso. Queste ottimizzazioni si applicano anche ad altre operazioni: ad esempio, l'esecuzione di un'operazione di aggiunta su due oggetti enormi. Come si abilitano queste ottimizzazioni delle operazioni di valore? Il compilatore li abilita in modo implicito, in modo analogo ai costruttori di copia può essere generato automaticamente dal compilatore. Tuttavia, la classe deve "acconsentire esplicitamente" per spostare le assegnazioni e spostare i costruttori dichiarandoli nella definizione della classe. Move usa il riferimento rvalue (&) double e commerciale nelle dichiarazioni di funzione membro appropriate e definisce il costruttore di spostamento e i metodi di assegnazione di spostamento. È anche necessario inserire il codice corretto per "rubare le intestino" dall'oggetto di origine.

Come si decide se è necessario abilitare le operazioni di spostamento? Se sai già che devi abilitare la costruzione di copie, probabilmente vuoi abilitare la costruzione di spostamenti, anche se è più economico di una copia completa. Tuttavia, se si sa che è necessario il supporto per lo spostamento, non significa necessariamente abilitare le operazioni di copia. Questo secondo caso sarebbe denominato "tipo di solo spostamento". Un esempio già presente nella libreria standard è unique_ptr. Come nota laterale, il vecchio auto_ptr è deprecato ed è stato sostituito con unique_ptr precisione a causa della mancanza di supporto semantica di spostamento nella versione precedente di C++.

Usando la semantica di spostamento, è possibile restituire per valore o inserire al centro. Lo spostamento è un'ottimizzazione della copia. Non è necessaria l'allocazione dell'heap come soluzione alternativa. Si consideri lo pseudocodice seguente:

#include <set>
#include <vector>
#include <string>
using namespace std;

//...
set<widget> LoadHugeData() {
    set<widget> ret;
    // ... load data from disk and populate ret
    return ret;
}
//...
widgets = LoadHugeData();   // efficient, no deep copy

vector<string> v = IfIHadAMillionStrings();
v.insert( begin(v)+v.size()/2, "scott" );   // efficient, no deep copy-shuffle
v.insert( begin(v)+v.size()/2, "Andrei" );  // (just 1M ptr/len assignments)
//...
HugeMatrix operator+(const HugeMatrix& , const HugeMatrix& );
HugeMatrix operator+(const HugeMatrix& ,       HugeMatrix&&);
HugeMatrix operator+(      HugeMatrix&&, const HugeMatrix& );
HugeMatrix operator+(      HugeMatrix&&,       HugeMatrix&&);
//...
hm5 = hm1+hm2+hm3+hm4+hm5;   // efficient, no extra copies

Abilitazione dello spostamento per i tipi valore appropriati

Per una classe simile a un valore in cui lo spostamento può essere più economico di una copia completa, abilitare la costruzione di spostamenti e l'assegnazione di spostamento per un'efficienza. Si consideri lo pseudocodice seguente:

#include <memory>
#include <stdexcept>
using namespace std;
// ...
class my_class {
    unique_ptr<BigHugeData> data;
public:
    my_class( my_class&& other )   // move construction
        : data( move( other.data ) ) { }
    my_class& operator=( my_class&& other )   // move assignment
    { data = move( other.data ); return *this; }
    // ...
    void method() {   // check (if appropriate)
        if( !data )
            throw std::runtime_error("RUNTIME ERROR: Insufficient resources!");
    }
};

Se si abilita la costruzione/assegnazione di copia, abilitare anche la costruzione/assegnazione di spostamento se può essere più conveniente di una copia completa.

Alcuni tipi non valore sono di tipo move-only, ad esempio quando non è possibile clonare una risorsa, trasferirne solo la proprietà. Esempio: unique_ptr.

Vedi anche

Sistema di tipi C++
Benvenuto in C++
Riferimenti al linguaggio C++
Libreria standard C++