C++
Sémantique de valeur et de référence
Recherche…
Copie en profondeur et support de déplacement
Si un type souhaite avoir une sémantique de valeur et qu'il doit stocker des objets dynamiquement alloués, alors, lors des opérations de copie, le type devra allouer de nouvelles copies de ces objets. Il doit également le faire pour la copie.
Ce type de copie s'appelle une "copie profonde". Il prend efficacement ce qui aurait été la sémantique de référence et la transforme en sémantique de valeur:
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;
}
};
La sémantique de déplacement permet à un type comme Value
d’éviter de copier réellement ses données référencées. Si l'utilisateur utilise la valeur d'une manière qui provoque un déplacement, le "copié" de l'objet peut être laissé vide des données référencées:
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_);
}
};
En effet, on peut même rendre un tel type non copiable, si l'on veut interdire les copies profondes tout en permettant de déplacer l'objet.
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_);
}
};
Nous pouvons même appliquer la règle de zéro, en utilisant l' 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;
};
Définitions
Un type a une sémantique de valeur si l'état observable de l'objet est fonctionnellement distinct de tous les autres objets de ce type. Cela signifie que si vous copiez un objet, vous avez un nouvel objet, et les modifications du nouvel objet ne seront en aucun cas visibles depuis l'ancien objet.
La plupart des types C ++ de base ont une sémantique de valeur:
int i = 5;
int j = i; //Copied
j += 20;
std::cout << i; //Prints 5; i is unaffected by changes to j.
La plupart des types définis de bibliothèque standard ont également une sémantique de valeur:
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.
Un type est dit avoir une sémantique de référence si une instance de ce type peut partager son état observable avec un autre objet (externe à celui-ci), de sorte que la manipulation d'un objet entraînera un changement d'état dans un autre objet.
Les pointeurs C ++ ont une sémantique de valeur par rapport à l'objet vers lequel ils pointent, mais ils ont une sémantique de référence en ce qui concerne l' état de l'objet vers lequel ils pointe:
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.
Les références C ++ ont également une sémantique de référence.