[英]Avoid repeating the same code in copy constructor and operator=
在 C++ 中,當類包含動態分配的數據時,顯式定義復制構造函數、operator= 和析構函數通常是合理的。 但是這些特殊方法的活動是重疊的。 更具體地說, operator= 通常首先進行一些破壞,然后進行類似於復制構造函數中的處理。
我的問題是如何在不重復相同代碼行且不需要處理器做不必要的工作(如不必要的復制)的情況下以最佳方式編寫此代碼。
我通常以兩種幫助方法結束。 一種用於建造,一種用於破壞。 第一個是從復制構造函數和 operator= 調用的。 第二個由析構函數和 operator= 使用。
這是示例代碼:
template <class T>
class MyClass
{
private:
// Data members
int count;
T* data; // Some of them are dynamicly allocated
void construct(const MyClass& myClass)
{
// Code which does deep copy
this->count = myClass.count;
data = new T[count];
try
{
for (int i = 0; i < count; i++)
data[i] = myClass.data[i];
}
catch (...)
{
delete[] data;
throw;
}
}
void destruct()
{
// Dealocate all dynamicly allocated data members
delete[] data;
}
public: MyClass(int count) : count(count)
{
data = new T[count];
}
MyClass(const MyClass& myClass)
{
construct(myClass);
}
MyClass& operator = (const MyClass& myClass)
{
if (this != &myClass)
{
destruct();
construct(myClass);
}
return *this;
}
~MyClass()
{
destruct();
}
};
這甚至正確嗎? 而且這樣拆分代碼是不是一個好習慣?
一個最初的評論: operator=
不是從破壞開始,而是從構造開始。 否則,如果構造通過異常終止,它將使對象處於無效狀態。 因此,您的代碼不正確。 (請注意,測試自賦值的必要性通常表明賦值運算符不正確。)
處理這個問題的經典解決方案是交換習語:你添加一個成員函數 swap:
void MyClass:swap( MyClass& other )
{
std::swap( count, other.count );
std::swap( data, other.data );
}
保證不會拋出。 (這里,它只是交換一個 int 和一個指針,兩者都不能拋出。)然后你將賦值運算符實現為:
MyClass& MyClass<T>::operator=( MyClass const& other )
{
MyClass tmp( other );
swap( tmp );
return *this;
}
這是簡單而直接的,但是在您開始更改數據之前完成所有可能失敗的操作的任何解決方案都是可以接受的。 對於像您的代碼這樣的簡單案例,例如:
MyClass& MyClass<T>::operator=( MyClass const& other )
{
T* newData = cloneData( other.data, other.count );
delete data;
count = other.count;
data = newData;
return *this;
}
(其中cloneData
是一個成員函數,它執行您的construct
所做的大部分工作,但返回指針,並且不修改this
任何內容)。
編輯:
不直接關系到你最初的問題,但總體上,在這種情況下,你不想做一個new T[count]
在cloneData
(或construct
,或其他)。 這將使用默認構造函數構造所有T
,然后分配它們。 這樣做的慣用方式是這樣的:
T*
MyClass<T>::cloneData( T const* other, int count )
{
// ATTENTION! the type is a lie, at least for the moment!
T* results = static_cast<T*>( operator new( count * sizeof(T) ) );
int i = 0;
try {
while ( i != count ) {
new (results + i) T( other[i] );
++ i;
}
} catch (...) {
while ( i != 0 ) {
-- i;
results[i].~T();
}
throw;
}
return results;
}
大多數情況下,這將使用單獨的(私有)管理器類來完成:
// Inside MyClass, private:
struct Data
{
T* data;
int count;
Data( int count )
: data( static_cast<T*>( operator new( count * sizeof(T) ) )
, count( 0 )
{
}
~Data()
{
while ( count != 0 ) {
-- count;
(data + count)->~T();
}
}
void swap( Data& other )
{
std::swap( data, other.data );
std::swap( count, other.count );
}
};
Data data;
// Copy constructor
MyClass( MyClass const& other )
: data( other.data.count )
{
while ( data.count != other.data.count ) {
new (data.data + data.count) T( other.date[data.count] );
++ data.count;
}
}
(當然,還有用於賦值的交換習語)。 這允許多個計數/數據對,而沒有任何失去異常安全的風險。
通過首先復制右側然后與之交換來實現分配。 通過這種方式,您還可以獲得異常安全,而上面的代碼沒有提供。 否則,在 destruct() 成功之后,construct() 失敗時,您可能最終會得到一個損壞的容器,因為成員指針引用了一些已釋放的數據,並且在銷毀時將再次釋放,導致未定義的行為。
foo&
foo::operator=(foo const& rhs)
{
using std::swap;
foo tmp(rhs);
swap(*this, tmp);
return *this;
}
我沒有看到任何固有的問題,只要你確保不聲明構造或破壞虛擬。
您可能對 Effective C++ (Scott Meyers) 中的第 2 章感興趣,該章完全致力於構造函數、復制運算符和析構函數。
至於您的代碼未按應有方式處理的異常,請考慮更有效的 C++ (Scott Meyers) 中的第 10 項和第 11 項。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.