[英]How to create copy constructor and destructor in a class with an array of pointers to objects where objects itself have an array of pointers to ints
在下面的代碼中,我有一個A類,它具有一個動態整數數組。 我還有另一個B類,該類具有指向AI類對象的指針的數組,已經為A類編寫了復制構造函數。我需要為B類編寫一個復制構造函數和析構函數,我嘗試了各種方法,但均未成功。
A類的定義:
class A {
public:
A::A(const A& other){
siz = other.siz;
c = other.c;
s = other.s;
e = other.e;
arr= new int[other.c];
memcpy(arr, other.arr, sizeof(int) * c);
}
A::~A() {
delete [] m_arr;
}
const A& operator=(const A& rhs){
if(this == &rhs)
return *this; // handling of self assignment
delete[] arr; // freeing previously used memory
arr = new int[rhs.c];
siz = rhs.siz;
c = rhs.c;
e = rhs.e;
s = rhs.s;
memcpy(m_arr, rhs.arr, sizeof(int) * c);
return *this;
}
private :
int *arr ;
int c ;
int siz ;
int s ;
int e ;
}
B類的定義:
class B {
public:
B::B(const B& other){
// .......need help here
}
B::~B() {
//......need help here
}
private :
static const int constant = 7;
A * array[constant] ;
int x ;
int y ;
int z ;
}
謝謝您的幫助
首先,我們假設無論出於何種原因,都不能使用std::vector
或std::array
。 假設到目前為止,我們遵循您的類的設計,則可以實現如下的復制構造函數:
B::B(const B &other)
{
for (std::size_t i = 0; i < constant; ++i) {
// Use `new` to allocate memory and also call `A`'s copy constructor.
array[i] = new A(*other.array[i]);
}
}
由於array
是指向A
s的指針的數組,所以它的作用是為數組中的每個元素分配動態內存,並使用new
指向指向這些動態分配的A
對象的指針來填充數組,同時還調用了已創建的復制構造函數為A
,使用語法new A(other_a)
它調用你的A::A(const A &other)
。 由於other
是對A
的引用,而不是對A
的指針,因此對other.array[i]
中other.array[i]
的指針進行了取消引用,這就是為什么調用是A(*other.array[i])
而不是A(other.array[i])
。
由於我們在這里使用new
分配了動態內存,因此析構函數必須為對new的每個調用都調用delete
。 可以類似地實現:
B::~B()
{
for (std::size_t i = 0; i < constant; ++i) {
// As each `A` has been allocated with `new`, they should now be
// destroyed.
delete array[i];
}
}
因此,我們現在所擁有的東西似乎可以按我們希望的那樣工作,並且我們可能會認為這就是全部。 但是,事情開始變得復雜,如果由new
執行的分配之一失敗並引發異常會發生什么? 還是A
的構造函數拋出異常怎么辦? 到目前為止,對於已使用new
分配的元素,永遠不會調用delete
。
為了使我們的副本構造函數異常安全,需要一些稍微復雜的代碼。 例如,如下所示:
B::B(const B &other)
{
std::size_t i;
try {
for (i = 0; i < constant; ++i) {
array[i] = new A(*other.array[i]);
}
} catch (...) {
// Delete all elements allocated so far
for (std::size_t d = 0; d < i; ++d) {
delete array[i];
}
// Re-throw the exception to the caller
throw;
}
}
這樣的代碼很快就會變得難以維護。 為避免此類問題,一個好的指導原則是,管理一個必須創建和銷毀的資源的生存期應由僅管理該資源的生存期的類封裝。 這很重要,因為如果您開始向類中添加類似於此數組的更多構造,那么您的類將負責構造和破壞的不僅僅是這個數組,這將使異常安全性比現在更加困難。 實際上,構造和破壞數組的原因已經很復雜了,這是因為您的類負責7個獨立資源(動態分配的對象)的生命周期,每個資源對應一個數組元素。
考慮到這一點,簡化此方法的方法是使用一個類,該類使用new
和delete
封裝動態分配和取消分配對象。 C ++ 11包括幾個類,它們至少封裝了釋放部分,其中最相關的是std::unique_ptr
和std::shared_ptr
。 但是,這些類旨在避免復制。 unique_ptr
明確地是不可復制的,並且復制shared_ptr
只會創建對相同資源的新引用,同時保留引用計數。 這意味着您仍然必須手動執行復制。
您可以通過將B
的聲明從以下位置更改為unique_ptr
:
A *array[constant];
至:
std::unique_ptr<A> array[constant];
然后,您可以使用以下方法填充復制構造函數中的每個成員:
array[i] = std::unique_ptr<A>(new A(*other.array[i]));
使用這種方法,您不必擔心捕獲異常,因為如果在構造函數中的某個地方拋出異常,則將為數組中的每個unique_ptr
自動調用析構函數。 尚未分配給的unique_ptr
缺省情況下將保留空指針,並且銷毀它們時安全地不執行任何操作。
但是,還有另一種方法:根本不使用指針/動態內存。 您已經有一個類( A
),該類負責其自身資源的生命周期。
為此,可以將B
的以下聲明更改為:
A *array[constant];
至:
A array[constant];
這意味着您不再需要定義副本構造函數(或副本分配運算符)。 如果C ++類中沒有提供復制構造函數,則該類可以被復制,就好像它具有簡單的逐成員復制構造函數一樣,該構造函數也適用於數組,並將為數組中的每個元素調用復制構造函數。 並且由於數組本身是類的一部分,並且不包含指向動態內存的指針,因此不需要使用delete
手動分配每個元素。
關鍵是將原始擁有的指針包裝到RAII資源管理器中 ,並定義組裝這些安全RAII構建塊的類。 然后,C ++編譯器將能夠自動合成復制操作和析構函數(以及移動操作)。
例如,在class A
您有一個int *arr
數據成員,該成員用於存儲擁有指向動態分配的數組的原始指針。 將其替換為RAII資源管理器,例如標准std::vector
容器(例如std::vector<int> arr
)。
這樣做無需定義顯式析構函數,因為編譯器將自動在向量數據成員上調用std::vector
析構函數,並且內存將自動釋放(無需調用顯式delete[]
)。
同樣,默認的復制構造函數將執行成員復制,因此std::vector
復制構造函數將由C ++編譯器自動調用,並且從源向量到目標向量的深層復制將自動發生,而無需您使用new[]
動態分配, memcpy
等“重新發明輪子”。
從C ++ 11開始,您可以告訴C ++編譯器使用此語法合成默認的復制構造函數(也可用於默認構造函數,移動構造函數,復制賦值運算符等)。
class A {
public:
...
A(const A&) = default;
...
};
對於class B
,也要考慮使用vector
,而不是A * array[constant]
數據成員,例如,指向A的智能指針的向量:
vector<shared_ptr<A>> arrayOfAs;
一種替代方法可能是將std::array
用於恆定大小的內容。
無論如何,關鍵是:考慮使用RAII資源管理器作為更復雜類的構建塊,而不是將原始擁有的指針和其他不安全的原始資源用作數據成員。 每個原始資源都應安全地包裝在其自己的RAII資源管理器中,然后應將其用作更復雜類的數據成員。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.