C++
Значение и справочная семантика
Поиск…
Поддержка глубокого копирования и перемещения
Если тип хочет иметь семантику значений, и ему нужно хранить объекты, которые динамически распределены, то при копировании операции тип должен будет выделять новые копии этих объектов. Он также должен сделать это для назначения копии.
Такой вид копирования называется «глубокой копией». Он эффективно принимает то, что в противном случае было бы ссылкой на семантику и превращало бы ее в семантику значений:
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;
}
};
Перемещение семантики допускает тип типа Value
чтобы избежать по-настоящему копирования своих данных, на которые ссылаются. Если пользователь использует значение таким образом, чтобы провоцировать ход, «скопированный» из объекта можно оставить пустым из данных, на которые он ссылается:
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_);
}
};
Действительно, мы можем даже сделать такой тип не скопируемым, если мы хотим запретить глубокие копии, все еще позволяя перемещать объект.
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_);
}
};
Мы можем даже применить правило Zero, используя 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;
};
Определения
Тип имеет семантику значений, если наблюдаемое состояние объекта функционально отличается от всех других объектов этого типа. Это означает, что если вы копируете объект, у вас есть новый объект, и модификации нового объекта никоим образом не будут видны из старого объекта.
Большинство базовых типов C ++ имеют семантику значений:
int i = 5;
int j = i; //Copied
j += 20;
std::cout << i; //Prints 5; i is unaffected by changes to j.
В большинстве стандартных типов стандартной библиотеки также есть семантика значений:
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.
Говорят, что тип имеет ссылочную семантику, если экземпляр этого типа может делиться своим наблюдаемым состоянием с другим объектом (внешним по отношению к нему), так что манипуляция одним объектом приведет к изменению состояния в другом объекте.
Указатели C ++ имеют семантику значений относительно того, к какому объекту они указывают, но имеют ссылочную семантику относительно состояния объекта, на который они указывают:
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.
Ссылки на C ++ также имеют ссылочную семантику.