C++
Semantica del valore e di riferimento
Ricerca…
Copia e supporto per muovere in profondità
Se un tipo desidera avere una semantica del valore e deve archiviare oggetti che vengono allocati dinamicamente, quindi in operazioni di copia, il tipo dovrà allocare nuove copie di tali oggetti. Deve anche farlo per l'assegnazione delle copie.
Questo tipo di copia è chiamato "copia profonda". Prende efficacemente quella che altrimenti sarebbe stata semantica di riferimento e la trasforma in semantica del valore:
struct Inner {int i;};
const int NUM_INNER = 5;
class Value
{
private:
Inner *array_; //Normally has reference semantics.
public:
Value() : array_(new Inner[NUM_INNER]){}
~Value() {delete[] array_;}
Value(const Value &val) : array_(new Inner[NUM_INNER])
{
for(int i = 0; i < NUM_INNER; ++i)
array_[i] = val.array_[i];
}
Value &operator=(const Value &val)
{
for(int i = 0; i < NUM_INNER; ++i)
array_[i] = val.array_[i];
return *this;
}
};
Spostare la semantica consente un tipo di Value
per evitare di copiare veramente i suoi dati di riferimento. Se l'utente usa il valore in un modo che provoca uno spostamento, il "copiato" dall'oggetto può essere lasciato vuoto dei dati a cui fa riferimento:
struct Inner {int i;};
constexpr auto NUM_INNER = 5;
class Value
{
private:
Inner *array_; //Normally has reference semantics.
public:
Value() : array_(new Inner[NUM_INNER]){}
//OK to delete even if nullptr
~Value() {delete[] array_;}
Value(const Value &val) : array_(new Inner[NUM_INNER])
{
for(int i = 0; i < NUM_INNER; ++i)
array_[i] = val.array_[i];
}
Value &operator=(const Value &val)
{
for(int i = 0; i < NUM_INNER; ++i)
array_[i] = val.array_[i];
return *this;
}
//Movement means no memory allocation.
//Cannot throw exceptions.
Value(Value &&val) noexcept : array_(val.array_)
{
//We've stolen the old value.
val.array_ = nullptr;
}
//Cannot throw exceptions.
Value &operator=(Value &&val) noexcept
{
//Clever trick. Since `val` is going to be destroyed soon anyway,
//we swap his data with ours. His destructor will destroy our data.
std::swap(array_, val.array_);
}
};
In effetti, possiamo persino rendere questo tipo non copiabili, se vogliamo proibire le copie profonde mentre ancora permettiamo che l'oggetto sia spostato.
struct Inner {int i;};
constexpr auto NUM_INNER = 5;
class Value
{
private:
Inner *array_; //Normally has reference semantics.
public:
Value() : array_(new Inner[NUM_INNER]){}
//OK to delete even if nullptr
~Value() {delete[] array_;}
Value(const Value &val) = delete;
Value &operator=(const Value &val) = delete;
//Movement means no memory allocation.
//Cannot throw exceptions.
Value(Value &&val) noexcept : array_(val.array_)
{
//We've stolen the old value.
val.array_ = nullptr;
}
//Cannot throw exceptions.
Value &operator=(Value &&val) noexcept
{
//Clever trick. Since `val` is going to be destroyed soon anyway,
//we swap his data with ours. His destructor will destroy our data.
std::swap(array_, val.array_);
}
};
Possiamo anche applicare la regola dello zero, attraverso l'uso di unique_ptr
:
struct Inner {int i;};
constexpr auto NUM_INNER = 5;
class Value
{
private:
unique_ptr<Inner []>array_; //Move-only type.
public:
Value() : array_(new Inner[NUM_INNER]){}
//No need to explicitly delete. Or even declare.
~Value() = default; {delete[] array_;}
//No need to explicitly delete. Or even declare.
Value(const Value &val) = default;
Value &operator=(const Value &val) = default;
//Will perform an element-wise move.
Value(Value &&val) noexcept = default;
//Will perform an element-wise move.
Value &operator=(Value &&val) noexcept = default;
};
definizioni
Un tipo ha semantica del valore se lo stato osservabile dell'oggetto è funzionalmente distinto da tutti gli altri oggetti di quel tipo. Ciò significa che se copi un oggetto, hai un nuovo oggetto e le modifiche del nuovo oggetto non saranno in alcun modo visibili dal vecchio oggetto.
I tipi C ++ di base hanno semantica del valore:
int i = 5;
int j = i; //Copied
j += 20;
std::cout << i; //Prints 5; i is unaffected by changes to j.
La maggior parte dei tipi definiti di libreria standard ha anche la semantica del valore:
std::vector<int> v1(5, 12); //array of 5 values, 12 in each.
std::vector<int> v2 = v1; //Copies the vector.
v2[3] = 6; v2[4] = 9;
std::cout << v1[3] << " " << v1[4]; //Writes "12 12", since v1 is unchanged.
Si dice che un tipo abbia una semantica di riferimento se un'istanza di quel tipo può condividere il suo stato osservabile con un altro oggetto (esterno ad esso), in modo tale che la manipolazione di un oggetto causerà lo stato di cambiare all'interno di un altro oggetto.
I puntatori C ++ hanno semantica del valore rispetto a quale oggetto puntano, ma hanno una semantica di riferimento rispetto allo stato dell'oggetto a cui puntano:
int *pi = new int(4);
int *pi2 = pi;
pi = new int(16);
assert(pi2 != pi); //Will always pass.
int *pj = pi;
*pj += 5;
std::cout << *pi; //Writes 9, since `pi` and `pj` reference the same object.
Anche i riferimenti C ++ hanno una semantica di riferimento.