2017-09-26 142 views
1

在下面的代码中,我有一个具有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 ;   
} 

感谢您的帮助

+6

为什么不使用'std :: vector'? – user463035818

+0

B :: B(const A&other){,为什么要在这里类A?它应该是B类 – Sumeet

+0

为A写一个代理操作,我可以帮忙 – doctorlove

回答

1

,关键是包装原料拥有指针到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

+0

我想知道这有多少好在其他情况下)答案将起居者明白..:/ – gsamaras

+0

@gsamaras我不知道。但我知道我尽了最大的努力写出了一份清晰明确的答案(在我的时间限制内),并尝试给OP提供一些有用的帮助。 –

0

我们首先假设对于此练习,无论出于何种原因,您都不能使用std::vectorstd::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个独立资源(动态分配的对象)的生命周期,每个数组元素一个。

考虑到这一点,一种简化这种方法的方法是使用一个封装动态分配和释放对象的类,其中包含newdelete。 C++ 11包含至少封装释放部分的几个类,最相关的是std::unique_ptrstd::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手动解除分配每个元素。

相关问题