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;
  }
};
C ++ 11

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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow