我有以下代码段:
#include <iostream>
using namespace std;
class A {
int* data;
int size;
public:
A(int s):size(s)
{
data = new int[size];
}
A() {
data = nullptr;
}
~A() {
if (data) delete [] data;
}
};
class B {
A a[2];
public:
B() {
a[0] = A(10);
a[1] = A(11);
}
};
int main(int argc, char *argv[]) {
B b;
}
在上面的C ++代码中,我有 A类,它有一个数组成员int* data
,并且(de)内存分配由(处理)去)构造函数。我创建了 B类,它具有固定长度的数组 数组作为数据成员。
我的问题是:如何优雅初始化会员A a[2]
?在上面的代码中,A(10)
和A(11)
在堆栈上创建,当跳出作用域时,它们的析构函数将被调用,因此数据无效。当跳过main
函数的作用域时,a[2]
所持有的指针将被解除分配两次,从而导致错误:
pointer being freed was not allocated
。
一种可能的解决方案是仔细设计copy constructor
和move constructor
,通过这样做,上述编码范例可行。
我尝试过的另一个解决方案是在class B
的初始化列表中初始化数组:
B() : a { A(10), A(11) }
此解决方案有效,我并没有真正告诉初始化列表的基本机制。我认为它必须与简单的构建和复制完全不同。我真的希望有些专家能够详细解释这种机制。当然,这种解决方案难以编码且不灵活。
所以我想知道C ++中是否有一些编程范例可以解决这个设计问题?
答案 0 :(得分:5)
在上面的代码中,A(10)和A(11)在堆栈上创建
它们是临时对象。未指定 在哪里创建或者根本不创建
。当跳出范围时,他们的析构函数将被称为
在相应的移动赋值语句结束后,将调用每个临时的析构函数。
一种可能的解决方案是仔细设计复制构造函数和移动构造函数,通过这样做,上述编码范例可以工作。
还有{copy,move}赋值运算符。当隐含声明的人不做正确的事情时,你应该总是这样做。如果你在析构函数中删除了某些内容,他们就永远不会做正确的事。
我尝试过的另一个解决方案是在B类初始化列表中初始化数组
这个解决方案有效,我并没有真正告诉初始化列表的基本机制。我认为它必须与简单的构造和复制完全不同。
原始代码中的错误是A
的移动赋值运算符。由于初始化列表永远不会从临时移动分配,因此它永远不会触发错误。
这实际上是构建您要求的a
更优雅的方式。不是因为它避免了这个错误,而是因为避免不必要的移动本质上就是好事。
所以我想知道C ++中是否有一些编程范例可以解决这个设计问题?
是。 RAII和 Single responsibility principle。除非你的类不做任何其他事情,除了管理data
指向的内存之外,它不应该管理内存。相反,它应该将内存管理委托给RAII对象。在这种情况下,您应该使用std::vector
成员。
class A {
std::vector<int> data;
public:
A(int s):data(s) {}
A() = default;
};
答案 1 :(得分:1)
使用初始化列表构造B :: a,如下所示:
class B {
A a[2];
public:
B() : a({10, 11}){
}
};
答案 2 :(得分:1)
理想的答案是强制A
使用动作而不是副本,或者在副本上为项目分配新空间。在这两者中,效率最高的是前者,所以我将在下面进行扩展:
强制运动可以两种方式完成:
operator=
,并实现自己的移动构造函数和operator=
std::move
和std::swap
。其中,前者优越,因为你不会意外地复制课程,但后者你正在移动的事实会更加明显。
要删除默认复制方法,请执行以下操作:
class A {
A( const A& a ) = delete;
A& operator =( const A& a ) = delete;
}