Buscar..


Copia profunda y soporte de movimiento.

Si un tipo desea tener una semántica de valor, y necesita almacenar objetos que se asignan dinámicamente, en las operaciones de copia, el tipo deberá asignar nuevas copias de esos objetos. También debe hacer esto para la copia de la asignación.

Este tipo de copia se llama "copia profunda". Toma efectivamente lo que de otro modo habría sido la semántica de referencia y lo convierte en semántica de valor:

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

La semántica de Move permite que un tipo como Value evite copiar realmente los datos a los que se hace referencia. Si el usuario utiliza el valor de una manera que provoca un movimiento, el objeto "copiado" se puede dejar vacío de los datos a los que hace referencia:

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_);
  }
};

De hecho, incluso podemos hacer que dicho tipo no se pueda copiar, si queremos prohibir las copias profundas y al mismo tiempo permitir que el objeto se mueva.

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_);
  }
};

Incluso podemos aplicar la Regla de cero, mediante el uso de 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;
};

Definiciones

Un tipo tiene valor semántico si el estado observable del objeto es funcionalmente distinto de todos los demás objetos de ese tipo. Esto significa que si copia un objeto, tiene un nuevo objeto y las modificaciones del nuevo objeto no serán visibles de ninguna manera desde el objeto anterior.

La mayoría de los tipos básicos de C ++ tienen valor semántico:

int i = 5;
int j = i; //Copied
j += 20;
std::cout << i; //Prints 5; i is unaffected by changes to j.

La mayoría de los tipos definidos de biblioteca estándar también tienen semántica de valor:

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.

Se dice que un tipo tiene semántica de referencia si una instancia de ese tipo puede compartir su estado observable con otro objeto (externo a él), de modo que la manipulación de un objeto hará que el estado cambie dentro de otro objeto.

Los punteros de C ++ tienen semántica de valor con respecto a qué objeto apuntan, pero tienen semántica de referencia con respecto al estado del objeto al que apuntan:

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.

Las referencias de C ++ también tienen semántica de referencia.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow