C++中的数组类成员初始化

4

我有以下代码片段:

#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++代码中,我有一个名为 class A 的类,该类具有一个名为 int * data 数组成员,并且内存的(de)分配由(de)构造函数处理。然后,我创建了一个名为 class B 的类,该类具有固定长度的 class A 数组作为数据成员。

我的问题是:如何优雅地初始化成员 A a [2] ?在上面的代码中, A(10) A(11)在堆栈上创建,当跳出作用域时,它们的析构函数将被调用,因此数据变得无效。当跳出 main 函数的作用域时, a [2] 持有的指针将被释放两次,导致错误:

释放的指针未被分配

一种可能的解决方案是仔细设计复制构造函数移动构造函数,通过这样做,上述编码范例可以正常工作。

我尝试过的另一种解决方案是在 class B 初始化列表中初始化数组:

B() : a { A(10), A(11) }

这个解决方案可行,但我并没有详细说明初始化列表的底层机制。我认为它肯定与简单的构造和复制非常不同。我真的希望一些专家能够详细解释这个机制。当然,这个解决方案是丑陋的硬编码,并且不够灵活。
所以我想知道在C++中是否有一些编程范式来解决这个设计问题?

3
在我们深入研究任何复杂的东西之前,不如先了解一下 if (data) delete [] data; 的含义——你查过 delete [] 的作用吗? - Kerrek SB
2
请查看五个规则以获取范例。请参阅std::vector以获取解决方案。 - Quentin
3个回答

5
在上面的代码中,A(10)和A(11)是在堆栈上创建的临时对象。没有指定它们被创建在哪里,或者它们是否被创建。
当跳出作用域时,它们的析构函数将被调用。每个临时对象的析构函数将在相应的移动赋值语句结束后被调用。
一种可能的解决方案是仔细设计复制构造函数和移动构造函数,通过这样做,上述编码范例可以工作。还有{copy,move}赋值运算符。如果隐式声明的赋值运算符不能正常工作,则始终应该这样做。如果在析构函数中删除了某些内容,则它们永远不会正常工作。
我尝试过的另一种解决方案是在类B的初始化列表中初始化数组。这个解决方案可行,并且我并不真正了解初始化列表的底层机制。我认为它一定与简单的构造和复制非常不同。
原始代码中的错误是A的移动赋值运算符表现不佳。由于初始化列表从不从临时变量进行移动分配,因此它永远不会触发错误。
这实际上是你要求的构建a的更优雅的方式。不是因为它避免了错误,而是因为避免不必要的移动本质上是一件好事。
“那么我想知道C++中是否有一些编程范式来解决这个设计问题呢?”
有的。 RAII 单一职责原则。除非你的类除了管理data指向的内存之外什么也不做,否则它不应该管理内存。相反,它应该将内存管理委托给RAII对象。在这种情况下,你应该使用一个std::vector成员。
class A {
    std::vector<int> data;
public:
    A(int s):data(s) {}
    A() = default;
};

1

使用初始化列表来构造B::a,像这样:

class B {
    A a[2];
public:
    B() : a({10, 11}){
    }
};

1
理想的答案是强制 A使用移动而不是拷贝,或在复制时为项目分配新空间。两者中,前者最有效,因此我将在下面详细说明:

强制移动可以通过两种方式实现:

  • 删除拷贝构造函数和拷贝operator=,并实现自己的移动构造函数和operator=
  • 一致使用std::movestd::swap

其中,前者更优,因为你将无法意外复制类,但后者的移动性会更明显。

要删除默认的拷贝方法,请执行以下操作:

class A {
    A( const A& a ) = delete;
    A& operator =( const A& a ) = delete;
}

我编辑了你的答案,希望现在更好了(如果您认为不是,请还原)。另外,据我所记得,声明移动操作(构造函数和/或赋值运算符)会自动删除复制操作 - 我是对的吗? - anatolyg
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Nonanon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接