如何在一个类中创建复制构造函数和析构函数,其中对象本身有一个指向数组的指针数组
在下面的代码中,我有一个具有int的动态数组的类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 ;
}
感谢您的帮助
,关键是包装原料拥有指针到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
,代替A * array[constant]
数据构件,可以考虑使用vector
,例如智能指针的矢量为A:
vector<shared_ptr<A>> arrayOfAs;
另一种可能使用std::array
为某物恒定的大小。
无论如何,关键是:考虑使用RAII资源管理器作为更复杂类的构建块,而不是使用原始指针和其他不安全的原始资源作为数据成员。每个原始资源应该安全地包装在自己的RAII资源管理器中,而这又应该用作更复杂类的数据成员。
P.S.作为奖励阅读考虑阅读"What is The Rule of Three? ",和这种follow up。
我想知道这有多少好在其他情况下)答案将起居者明白..:/ – gsamaras
@gsamaras我不知道。但我知道我尽了最大的努力写出了一份清晰明确的答案(在我的时间限制内),并尝试给OP提供一些有用的帮助。 –
我们首先假设对于此练习,无论出于何种原因,您都不能使用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
秒的阵列,是在每一个元素动态分配内存使用new
指向这些动态分配的A
对象的指针,同时还使用语法new A(other_a)
(调用A::A(const A &other)
)调用您为A
所做的复制构造函数来填充数组。由于other
是对A
的引用,而不是指向A
的指针,因此保留在other.array[i]
中的指针被取消引用,这就是为什么呼叫是A(*other.array[i])
而不是A(other.array[i])
。
由于我们在这里分配了动态存储器new
,析构函数必须调用delete
来调用`new'。这同样可以实现这样:
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
的构造函数抛出异常呢? delete
将永远不会被调用到目前为止已被分配到new
的元素。
为了使我们的复制构造函数异常安全,需要一些稍微复杂的代码。例如,如下所示:
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
仅创建对同一资源的新引用,同时保留引用计数。这意味着您仍然需要手动执行复制。
A *array[constant];
到:
你可以通过改变你的声明中B
从切换到unique_ptr
std::unique_ptr<A> array[constant];
然后,你可以填充每个成员在您的拷贝构造函数:
array[i] = std::unique_ptr<A>(new A(*other.array[i]));
用这种方法,你不会再不必担心捕获异常,因为如果在构造函数中某处抛出异常,将自动为数组中的每个unique_ptr
调用析构函数。尚未分配到的unique_ptr
默认情况下会保留空指针,并且在销毁时不会安全地执行任何操作。
然而,还有一种方法:根本不使用指针/动态内存。你已经有一个类(A
),它负责自己的资源的生命周期。
A *array[constant];
到:
要做到这一点,在B
以下声明可从改变
A array[constant];
这将意味着你不再需要定义一个拷贝构造函数(或复制根本不在意。如果在C++类中没有提供复制构造函数,则可以复制该类,就好像它具有简单的成员复制构造函数,该构造函数也适用于数组,并且将为数组中的每个元素调用复制构造函数。由于数组本身是类的一部分并且不包含指向动态内存的指针,因此不需要使用delete
手动解除分配每个元素。
为什么不使用'std :: vector'? – user463035818
B :: B(const A&other){,为什么要在这里类A?它应该是B类 – Sumeet
为A写一个代理操作,我可以帮忙 – doctorlove