Constructors verplaatsen en toewijzingsoperatoren verplaatsen (C++)

In dit onderwerp wordt beschreven hoe u een verplaatsingsconstructor en een toewijzingsoperator voor een C++-klasse schrijft. Met een verplaatsingsconstructor kunnen de resources die eigendom zijn van een rvalue-object, worden verplaatst naar een lvalue zonder te kopiëren. Zie Rvalue Reference Declarator: &&voor meer informatie over het verplaatsen van semantiek.

Dit onderwerp is gebaseerd op de volgende C++-klasse, MemoryBlockwaarmee een geheugenbuffer wordt beheerd.

// MemoryBlock.h
#pragma once
#include <iostream>
#include <algorithm>

class MemoryBlock
{
public:

   // Simple constructor that initializes the resource.
   explicit MemoryBlock(size_t length)
      : _length(length)
      , _data(new int[length])
   {
      std::cout << "In MemoryBlock(size_t). length = "
                << _length << "." << std::endl;
   }

   // Destructor.
   ~MemoryBlock()
   {
      std::cout << "In ~MemoryBlock(). length = "
                << _length << ".";

      if (_data != nullptr)
      {
         std::cout << " Deleting resource.";
         // Delete the resource.
         delete[] _data;
      }

      std::cout << std::endl;
   }

   // Copy constructor.
   MemoryBlock(const MemoryBlock& other)
      : _length(other._length)
      , _data(new int[other._length])
   {
      std::cout << "In MemoryBlock(const MemoryBlock&). length = "
                << other._length << ". Copying resource." << std::endl;

      std::copy(other._data, other._data + _length, _data);
   }

   // Copy assignment operator.
   MemoryBlock& operator=(const MemoryBlock& other)
   {
      std::cout << "In operator=(const MemoryBlock&). length = "
                << other._length << ". Copying resource." << std::endl;

      if (this != &other)
      {
         // Free the existing resource.
         delete[] _data;

         _length = other._length;
         _data = new int[_length];
         std::copy(other._data, other._data + _length, _data);
      }
      return *this;
   }

   // Retrieves the length of the data resource.
   size_t Length() const
   {
      return _length;
   }

private:
   size_t _length; // The length of the resource.
   int* _data; // The resource.
};

In de volgende procedures wordt beschreven hoe u een verplaatsingsconstructor en een verplaatsingstoewijzingsoperator schrijft voor de voorbeeldklasse C++.

Een verplaatsingsconstructor maken voor een C++-klasse

  1. Definieer een lege constructormethode die een rvalue-verwijzing naar het klassetype als parameter gebruikt, zoals wordt weergegeven in het volgende voorbeeld:

    MemoryBlock(MemoryBlock&& other)
       : _data(nullptr)
       , _length(0)
    {
    }
    
  2. Wijs in de verplaatsingsconstructor de klassegegevensleden van het bronobject toe aan het object dat wordt samengesteld:

    _data = other._data;
    _length = other._length;
    
  3. Wijs de gegevensleden van het bronobject toe aan standaardwaarden. Dit voorkomt dat de destructor meerdere keren resources (zoals geheugen) vrijgeeft:

    other._data = nullptr;
    other._length = 0;
    

Een verplaatsingstoewijzingsoperator maken voor een C++-klasse

  1. Definieer een lege toewijzingsoperator die een rvalue-verwijzing naar het klassetype als parameter gebruikt en retourneert een verwijzing naar het klassetype, zoals wordt weergegeven in het volgende voorbeeld:

    MemoryBlock& operator=(MemoryBlock&& other)
    {
    }
    
  2. Voeg in de verplaatsingstoewijzingsoperator een voorwaardelijke instructie toe die geen bewerking uitvoert als u het object aan zichzelf probeert toe te wijzen.

    if (this != &other)
    {
    }
    
  3. Maak in de voorwaardelijke instructie alle resources (zoals geheugen) vrij van het object waaraan wordt toegewezen.

    In het volgende voorbeeld wordt het _data lid vrijgemaakt uit het object waaraan wordt toegewezen:

    // Free the existing resource.
    delete[] _data;
    

    Volg stap 2 en 3 in de eerste procedure om de gegevensleden van het bronobject over te dragen naar het object dat wordt samengesteld:

    // Copy the data pointer and its length from the
    // source object.
    _data = other._data;
    _length = other._length;
    
    // Release the data pointer from the source object so that
    // the destructor does not free the memory multiple times.
    other._data = nullptr;
    other._length = 0;
    
  4. Retourneert een verwijzing naar het huidige object, zoals wordt weergegeven in het volgende voorbeeld:

    return *this;
    

Voorbeeld: Constructor en toewijzingsoperator voor verplaatsen voltooien

In het volgende voorbeeld ziet u de volledige verplaatsingsconstructor en de toewijzingsoperator voor de MemoryBlock klasse:

// Move constructor.
MemoryBlock(MemoryBlock&& other) noexcept
   : _data(nullptr)
   , _length(0)
{
   std::cout << "In MemoryBlock(MemoryBlock&&). length = "
             << other._length << ". Moving resource." << std::endl;

   // Copy the data pointer and its length from the
   // source object.
   _data = other._data;
   _length = other._length;

   // Release the data pointer from the source object so that
   // the destructor does not free the memory multiple times.
   other._data = nullptr;
   other._length = 0;
}

// Move assignment operator.
MemoryBlock& operator=(MemoryBlock&& other) noexcept
{
   std::cout << "In operator=(MemoryBlock&&). length = "
             << other._length << "." << std::endl;

   if (this != &other)
   {
      // Free the existing resource.
      delete[] _data;

      // Copy the data pointer and its length from the
      // source object.
      _data = other._data;
      _length = other._length;

      // Release the data pointer from the source object so that
      // the destructor does not free the memory multiple times.
      other._data = nullptr;
      other._length = 0;
   }
   return *this;
}

Voorbeeld: semantiek verplaatsen gebruiken om de prestaties te verbeteren

In het volgende voorbeeld ziet u hoe verplaatsingssemantiek de prestaties van uw toepassingen kan verbeteren. In het voorbeeld worden twee elementen aan een vectorobject toegevoegd en wordt vervolgens een nieuw element tussen de twee bestaande elementen ingevoegd. De vector klasse maakt gebruik van semantiek verplaatsen om de invoegbewerking efficiënt uit te voeren door de elementen van de vector te verplaatsen in plaats van ze te kopiëren.

// rvalue-references-move-semantics.cpp
// compile with: /EHsc
#include "MemoryBlock.h"
#include <vector>

using namespace std;

int main()
{
   // Create a vector object and add a few elements to it.
   vector<MemoryBlock> v;
   v.push_back(MemoryBlock(25));
   v.push_back(MemoryBlock(75));

   // Insert a new element into the second position of the vector.
   v.insert(v.begin() + 1, MemoryBlock(50));
}

In dit voorbeeld wordt de volgende uitvoer gegenereerd:

In MemoryBlock(size_t). length = 25.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In ~MemoryBlock(). length = 0.
In MemoryBlock(size_t). length = 75.
In MemoryBlock(MemoryBlock&&). length = 75. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In MemoryBlock(size_t). length = 50.
In MemoryBlock(MemoryBlock&&). length = 50. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 75. Moving resource.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.

Vóór Visual Studio 2010 heeft dit voorbeeld de volgende uitvoer geproduceerd:

In MemoryBlock(size_t). length = 25.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In MemoryBlock(size_t). length = 75.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In MemoryBlock(const MemoryBlock&). length = 75. Copying resource.
In ~MemoryBlock(). length = 75. Deleting resource.
In MemoryBlock(size_t). length = 50.
In MemoryBlock(const MemoryBlock&). length = 50. Copying resource.
In MemoryBlock(const MemoryBlock&). length = 50. Copying resource.
In operator=(const MemoryBlock&). length = 75. Copying resource.
In operator=(const MemoryBlock&). length = 50. Copying resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.

De versie van dit voorbeeld die gebruikmaakt van verplaatsingssemantiek is efficiënter dan de versie die geen semantiek voor verplaatsingen gebruikt, omdat er minder kopieer-, geheugentoewijzings- en geheugentoewijzingsbewerkingen worden uitgevoerd.

Robuuste programmering

Om resourcelekken te voorkomen, kunt u altijd gratis resources (zoals geheugen, bestandsingangen en sockets) in de verplaatsingstoewijzingsoperator gebruiken.

Als u de onherstelbare vernietiging van resources wilt voorkomen, moet u zelftoewijzing in de operator voor verplaatsingstoewijzing op de juiste manier afhandelen.

Als u zowel een verplaatsingsconstructor als een verplaatsingstoewijzingsoperator voor uw klasse opgeeft, kunt u overbodige code elimineren door de verplaatsingsconstructor te schrijven om de operator voor de verplaatsingstoewijzing aan te roepen. In het volgende voorbeeld ziet u een herziene versie van de verplaatsingsconstructor die de operator voor de verplaatsingstoewijzing aanroept:

// Move constructor.
MemoryBlock(MemoryBlock&& other) noexcept
   : _data(nullptr)
   , _length(0)
{
   *this = std::move(other);
}

De functie std::move converteert de lvalue other naar een rvalue.

Zie ook

Rvalue Reference Declarator: &&
std::move