C++
Semantyka wartości i odniesienia
Szukaj…
Obsługa głębokiego kopiowania i przenoszenia
Jeśli typ chce mieć semantykę wartości i musi przechowywać obiekty, które są dynamicznie przydzielane, to przy operacjach kopiowania typ będzie musiał przydzielić nowe kopie tych obiektów. Musi to również zrobić w celu przypisania kopii.
Ten rodzaj kopiowania nazywa się „głęboką kopią”. Skutecznie przyjmuje to, co w innym przypadku byłoby semantyką odniesienia i zamienia ją w semantykę wartości:
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;
}
};
Przenieś semantykę pozwala typowi typu Value
uniknąć prawdziwego kopiowania danych, do których się odwołuje. Jeśli użytkownik użyje tej wartości w sposób, który wywołuje ruch, „skopiowane” z obiektu można pozostawić puste dla danych, do których się odnosi:
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_);
}
};
Rzeczywiście, możemy nawet uniemożliwić kopiowanie takiego typu, jeśli chcemy zabronić wykonywania głębokich kopii, jednocześnie pozwalając na przesuwanie obiektu.
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_);
}
};
Możemy nawet zastosować Regułę Zera, używając 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;
};
Definicje
Typ ma semantykę wartości, jeśli obserwowalny stan obiektu jest funkcjonalnie odrębny od wszystkich innych obiektów tego typu. Oznacza to, że jeśli skopiujesz obiekt, otrzymasz nowy obiekt, a modyfikacje nowego obiektu nie będą w żaden sposób widoczne ze starego obiektu.
Większość podstawowych typów C ++ ma semantykę wartości:
int i = 5;
int j = i; //Copied
j += 20;
std::cout << i; //Prints 5; i is unaffected by changes to j.
Większość typów zdefiniowanych w bibliotece standardowej ma również semantykę wartości:
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.
Mówi się, że typ ma semantykę odniesienia, jeśli instancja tego typu może dzielić swój obserwowalny stan z innym obiektem (zewnętrznym względem niego), tak że manipulowanie jednym obiektem spowoduje zmianę stanu w innym obiekcie.
Wskaźniki C ++ mają semantykę wartości w odniesieniu do obiektu, na który wskazują, ale mają semantykę odniesienia w odniesieniu do stanu obiektu, na który wskazują:
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.
Odwołania w C ++ również mają semantykę odniesienia.