Bagikan melalui


Pindahkan Konstruktor dan Pindahkan Operator Penugasan (C++)

Topik ini menjelaskan cara menulis konstruktor pemindahan dan operator penetapan pemindahan untuk kelas C++. Konstruktor pemindahan memungkinkan sumber daya yang dimiliki oleh objek rvalue dipindahkan ke lvalue tanpa menyalin. Untuk informasi selengkapnya tentang memindahkan semantik, lihat Deklarator Referensi Rvalue: &&&.

Topik ini dibangun berdasarkan kelas C++ berikut, MemoryBlock, yang mengelola buffer memori.

// 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.
};

Prosedur berikut menjelaskan cara menulis konstruktor pemindahan dan operator penetapan pemindahan untuk contoh kelas C++.

Untuk membuat konstruktor pemindahan untuk kelas C++

  1. Tentukan metode konstruktor kosong yang mengambil referensi rvalue ke jenis kelas sebagai parameternya, seperti yang ditunjukkan dalam contoh berikut:

    MemoryBlock(MemoryBlock&& other)
       : _data(nullptr)
       , _length(0)
    {
    }
    
  2. Di konstruktor pemindahan, tetapkan anggota data kelas dari objek sumber ke objek yang sedang dibangun:

    _data = other._data;
    _length = other._length;
    
  3. Tetapkan anggota data objek sumber ke nilai default. Ini mencegah destruktor membebaskan sumber daya (seperti memori) beberapa kali:

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

Untuk membuat operator penetapan pemindahan untuk kelas C++

  1. Tentukan operator penugasan kosong yang mengambil referensi rvalue ke jenis kelas sebagai parameternya dan mengembalikan referensi ke jenis kelas, seperti yang ditunjukkan dalam contoh berikut:

    MemoryBlock& operator=(MemoryBlock&& other)
    {
    }
    
  2. Di operator penetapan pemindahan, tambahkan pernyataan bersyarkat yang tidak melakukan operasi jika Anda mencoba menetapkan objek ke dirinya sendiri.

    if (this != &other)
    {
    }
    
  3. Dalam pernyataan kondisi, bebaskan sumber daya apa pun (seperti memori) dari objek yang sedang ditetapkan.

    Contoh berikut membebaskan _data anggota dari objek yang ditetapkan ke:

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

    Ikuti langkah 2 dan 3 dalam prosedur pertama untuk mentransfer anggota data dari objek sumber ke objek yang sedang dibangun:

    // 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. Kembalikan referensi ke objek saat ini, seperti yang ditunjukkan dalam contoh berikut:

    return *this;
    

Contoh: Menyelesaikan pemindahan konstruktor dan operator penugasan

Contoh berikut menunjukkan konstruktor pemindahan lengkap dan operator penetapan MemoryBlock pemindahan untuk kelas:

// 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;
}

Contoh Gunakan semantik pemindahan untuk meningkatkan performa

Contoh berikut menunjukkan bagaimana semantik pemindahan dapat meningkatkan performa aplikasi Anda. Contoh menambahkan dua elemen ke objek vektor lalu menyisipkan elemen baru di antara dua elemen yang ada. Kelas vector menggunakan semantik pemindahan untuk melakukan operasi penyisipan secara efisien dengan memindahkan elemen vektor alih-alih menyalinnya.

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

Contoh ini menghasilkan output berikut:

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.

Sebelum Visual Studio 2010, contoh ini menghasilkan output berikut:

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.

Versi contoh ini yang menggunakan semantik pemindahan lebih efisien daripada versi yang tidak menggunakan semantik pemindahan karena melakukan lebih sedikit operasi salin, alokasi memori, dan dealokasi memori.

Pemrograman yang Kuat

Untuk mencegah kebocoran sumber daya, selalu gratiskan sumber daya (seperti memori, handel file, dan soket) di operator penetapan pemindahan.

Untuk mencegah penghancuran sumber daya yang tidak dapat dipulihkan, tangani penugasan mandiri dengan benar di operator penetapan pemindahan.

Jika Anda menyediakan konstruktor pemindahan dan operator penetapan pemindahan untuk kelas Anda, Anda dapat menghilangkan kode redundan dengan menulis konstruktor pemindahan untuk memanggil operator penetapan pemindahan. Contoh berikut menunjukkan versi konstruktor pemindahan yang direvisi yang memanggil operator penetapan pemindahan:

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

Fungsi std::move mengonversi lvalue other menjadi rvalue.

Baca juga

Deklarator Referensi Rvalue: && &
std::move