詳解C++中對構造函數(shù)和賦值運算符的復制和移動操作
復制構造函數(shù)和復制賦值運算符
從 C++ 11 中開始,該語言支持兩種類型的分配:復制賦值和移動賦值。 在本文中,“賦值”意味著復制賦值,除非有其他顯式聲明。 賦值操作和初始化操作都會導致對象被復制。
賦值:在將一個對象的值賦給另一個對象時,第一個對象將復制到第二個對象中。 因此,
Point a, b; ... a = b;
導致 b 的值被復制到 a 中。
初始化:在以下情況下將進行初始化:聲明新對象、參數(shù)通過值傳遞給函數(shù)或值通過值從函數(shù)返回。
您可以為類類型的對象定義“復制”的語義。 例如,考慮此代碼:
TextFile a, b; a.Open( "FILE1.DAT" ); b.Open( "FILE2.DAT" ); b = a;
前面的代碼可能表示“將 FILE1.DAT 的內容復制到 FILE2.DAT”,也可能表示“忽略 FILE2.DAT 并使 b 成為 FILE1.DAT 的另一個句柄”。 您必須將適當?shù)膹椭普Z義附加到每個類,如下所示。
通過將賦值運算符 operator= 與對類類型的引用一起用作返回類型和 const 引用所傳遞的參數(shù)(例如,ClassName& operator=(const ClassName& x);)。
通過通過復制構造函數(shù)。 有關復制構造函數(shù)的詳細信息,請參閱聲明構造函數(shù)的規(guī)則。
如果不聲明復制構造函數(shù),編譯器將為你生成 member-wise 復制構造函數(shù)。 如果不聲明復制賦值運算符,編譯器將為你生成 member-wise 復制賦值運算符。 聲明復制構造函數(shù)不會取消編譯器生成的復制賦值運算符,反之亦然。 如果實現(xiàn)上述其中一項,建議您還實現(xiàn)另一項以使代碼的含義變得明確。
逐個成員賦值和初始化 中更詳細地介紹了逐個成員賦值。
復制構造函數(shù)采用了 class-name& 類型的參數(shù),其中 class-name 是為其定義構造函數(shù)的類的名稱。 例如:
// spec1_copying_class_objects.cpp
class Window
{
public:
Window( const Window& ); // Declare copy constructor.
// ...
};
int main()
{
}
說明:
盡可能創(chuàng)建該類型的復制構造函數(shù)的參數(shù) const class-name&。 這可防止復制構造函數(shù)意外更改從中復制它的對象。 它還支持從 const 對象進行復制。
編譯器生成的構造函數(shù)
編譯器生成的復制構造函數(shù)(如用戶定義的復制構造函數(shù))具有單個參數(shù)類型“對 class-name 的引用”。 當所有基類和成員類都具有聲明為采用 const class-name& 類型的單個參數(shù)的復制構造函數(shù)時,將引發(fā)異常。 在這種情況下,編譯器生成的復制構造函數(shù)的參數(shù)也是 const。
當復制構造函數(shù)的參數(shù)類型不是 const 時,通過復制 const 對象進行初始化將產(chǎn)生錯誤。 反之則不然:如果參數(shù)是 const,您可以通過復制不是 const 的對象進行初始化。
編譯器生成的賦值運算符遵循關于 const 的相同模式。 除非所有基類和成員類中的賦值運算符都采用 const class-name& 類型的參數(shù),否則它們將采用 class-name& 類型的單個參數(shù)。 在這種情況下,類的生成的賦值運算符采用 const 參數(shù)。
說明:
當虛擬基類由復制構造函數(shù)(編譯器生成或用戶定義的)初始化時,將只初始化這些基類一次:在構造它們時。
含義類似于復制構造函數(shù)的含義。 當參數(shù)類型不是 const 時,從 const 對象賦值將產(chǎn)生錯誤。 反之則不然:如果將 const 值賦給不是 const 的值,則賦值能成功。
移動構造函數(shù)和移動賦值運算符
下面的示例基于用于管理內存緩沖區(qū)的 C++ 類 MemoryBlock。
// 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.
};
以下過程介紹如何為示例 C++ 類編寫移動構造函數(shù)和移動賦值運算符。
為 C++ 創(chuàng)建移動構造函數(shù)
定義一個空的構造函數(shù)方法,該方法采用一個對類類型的右值引用作為參數(shù),如以下示例所示:
MemoryBlock(MemoryBlock&& other)
: _data(nullptr)
, _length(0)
{
}
在移動構造函數(shù)中,將源對象中的類數(shù)據(jù)成員添加到要構造的對象:
_data = other._data; _length = other._length;
將源對象的數(shù)據(jù)成員分配給默認值。 這樣可以防止析構函數(shù)多次釋放資源(如內存):
other._data = nullptr; other._length = 0;
為 C++ 類創(chuàng)建移動賦值運算符
定義一個空的賦值運算符,該運算符采用一個對類類型的右值引用作為參數(shù)并返回一個對類類型的引用,如以下示例所示:
MemoryBlock& operator=(MemoryBlock&& other)
{
}
在移動賦值運算符中,如果嘗試將對象賦給自身,則添加不執(zhí)行運算的條件語句。
if (this != &other)
{
}
在條件語句中,從要將其賦值的對象中釋放所有資源(如內存)。
以下示例從要將其賦值的對象中釋放 _data 成員:
// Free the existing resource. delete[] _data;
執(zhí)行第一個過程中的步驟 2 和步驟 3 以將數(shù)據(jù)成員從源對象轉移到要構造的對象:
// 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;
以下示例顯示了 MemoryBlock 類的完整移動構造函數(shù)和移動賦值運算符:
// Move constructor.
MemoryBlock(MemoryBlock&& other)
: _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)
{
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;
}
以下示例演示移動語義如何能提高應用程序的性能。此示例將兩個元素添加到一個矢量對象,然后在兩個現(xiàn)有元素之間插入一個新元素。在 Visual C++ 2010 中,vector 類使用移動語義,通過移動矢量元素(而非復制它們)來高效地執(zhí)行插入操作。
// 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));
}
該示例產(chǎn)生下面的輸出:
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 = 25. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(MemoryBlock&&). length = 75. Moving resource. In ~MemoryBlock(). length = 0. In MemoryBlock(size_t). length = 50. In MemoryBlock(MemoryBlock&&). length = 50. Moving resource. In MemoryBlock(MemoryBlock&&). length = 50. Moving resource. In operator=(MemoryBlock&&). length = 75. In operator=(MemoryBlock&&). length = 50. 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.
使用移動語義的此示例版本比不使用移動語義的版本更高效,因為前者執(zhí)行的復制、內存分配和內存釋放操作更少。
可靠編程
若要防止資源泄漏,請始終釋放移動賦值運算符中的資源(如內存、文件句柄和套接字)。
若要防止不可恢復的資源損壞,請正確處理移動賦值運算符中的自我賦值。
如果為您的類同時提供了移動構造函數(shù)和移動賦值運算符,則可以編寫移動構造函數(shù)來調用移動賦值運算符,從而消除冗余代碼。以下示例顯示了調用移動賦值運算符的移動構造函數(shù)的修改后的版本:
// Move constructor.
MemoryBlock(MemoryBlock&& other)
: _data(nullptr)
, _length(0)
{
*this = std::move(other);
}

