C++中初始化动态数组的正确方法

7

我正在从事一个C++项目,其中频繁出现动态数组。 我想知道使用new运算符初始化动态数组的正确方法是什么?我的同事告诉我,在构造函数中使用new是不好的做法,因为构造函数应该是不容易出错或根本不会失败的构造函数。现在考虑以下示例:我们有两个类,一个比较复杂的State类和一个应该是自解释的StateContainer类。

class State {
private:
 unsigned smth;
public:
 State();
 State( unsigned s );
};

class StateContainer {
private:
 unsigned long nStates;
 State *states;
public:
 StateContainer();
 StateContainer( unsigned long n );
 virtual ~StateContainer();
};

StateContainer::StateContainer() {
 nStates = SOME_DEFINE_N_STATES;
 states = new State[nStates];
 if ( !states ) {
  // Error handling
 }
}

StateContainer::StateContainer( unsigned long n ) {
 nStates = n;
 try {
  states = new State[nStates]
 } catch ( std::bad_alloc &e ) {
  // Error handling
 }
}

StateContainer::~StateContainer() {
 if ( states ) {
  delete[] states;
  states = 0;
 }
}

现在,我有两个问题:

1.) 在构造函数中调用new是否可以,还是最好为State-Array创建一个额外的init()方法?为什么?

2.) 检查new是否成功的最佳方法是什么:

if (!ptr) std::cerr << "new failed."

或者

try { /*new*/ } catch (std::bad_alloc) { /*handling*/ } 

3.) 好的,有三个问题 ;o) 在内部实现上,new 关键字会执行一些操作

ptr = (Struct *)malloc(N*sizeof(Struct));

然后调用构造函数,对吗?


8
你不能使用 vector 吗? - GManNickG
你的同事是错的。在构造函数中使用 new 是完全可以接受的。 - Nathan Ernst
6个回答

7
你应该让 std::bad_alloc 异常传播——因为你可能无法采取任何合理的措施。
首先,从构造函数中抛出异常是唯一可靠的信号表明存在问题——如果没有异常,则表示对象已完全构造。因此,仅捕获 std::bad_alloc 将无助于其他可能的异常。
那么你能做些什么来“处理”它,以便其他代码知道并可以适当地做出反应呢?
正确使用异常——让它们传播到可以合理处理它们的位置

5

构造函数的整个目的是构造一个对象,包括初始化。当构造函数执行完成时,对象应该可以使用。这意味着构造函数应该执行任何必要的初始化。

你朋友建议的做法会导致代码难以维护和容易出错,并违反了 C++ 的所有良好实践。他是错误的。

至于动态数组,请使用 std::vector 代替。并且要初始化它,只需向构造函数传递一个参数:

std::vector<int>(10, 20)

将创建一个由10个整数组成的向量,它们都被初始化为值20。


“includes”是什么意思?对我来说,“构造”和“初始化”是同义词。 - fredoverflow

1
简短回答:
不,你的朋友是错的。构造函数是你进行分配和初始化的地方。我们甚至有一个术语叫做“资源获取即初始化”(RAII)...类在构造函数中作为其初始化的一部分获取资源,并在其析构函数中释放这些获取的资源。 长回答:
考虑以下代码在构造函数中的情况:
member1  = new whatever1[n];
member2  = new whatever2[m];

现在,假设在上面的代码中第二个分配内存的操作抛出了异常,可能是因为whatever2的构造失败并抛出了异常,或者是分配内存失败并抛出了std::bad_alloc异常。结果是你为whatever1分配的内存将会泄漏。

因此,最好使用诸如std::vector之类的容器类:

MyClass::MyClass(std::size_t n, std::size_t m) : member1(n), member2(m) {}
// where member1 and member2 are of type std::vector

当使用类型std::vector时,如果第二次分配失败,则会调用先前std::vector的析构函数,从而释放相应的资源。这可能是你的朋友说你不应该使用new(而应该使用容器类)的意思,在这种情况下,他大多数是正确的...尽管有一些智能指针类,如boost::shared_ptr,它们为您提供了相同的安全保证,您仍然需要使用new,所以他还不完全正确。

请注意,如果您只有一个正在分配的对象/数组,则这不是问题...这是您代码中的情况...您不必担心由于其他分配失败而导致泄漏。此外,我应该补充说明,new要么成功,要么抛出异常;它不会返回NULL,因此检查是无意义的。唯一需要捕获std::bad_alloc的情况是在您被迫进行多个分配(禁止使用std::vector)的情况下,在这种情况下,您需要在处理程序中释放其他资源,但然后重新抛出异常,因为您应该让它传播。


1

这不是完整的答案,只是我的个人看法:

1:我会在构造函数中使用new,但对于动态数组,STL是更好的选择。

2:new的常规错误处理是引发异常,因此检查返回的指针是无用的。

3:不要忘记new运算符,让故事变得更有趣一些。


0

你的朋友有一定道理。然而,在构造函数中进行内存预留并在析构函数中进行释放是常见做法。

构造函数中可能失败的方法存在问题:构造函数没有传统的方式通知调用者出现了问题。调用者期望得到一个没有任何损坏的对象实例。

解决这个问题的方法是抛出/传播异常。异常总是可以通过并且可以由调用者处理。


你的回答有矛盾之处;你说没有传统的通知用户错误的方式,然后又描述了一种。 - Terry Mahaffey
异常并不受欢迎,大多数C++只在必要时使用它们。在他们的“传统”思维中,没有异常的位置,这就是为什么他们不将其视为通知用户的一种方式。 - ypnos

0
  1. 如果您正在寻找返回类型,即函数必须返回状态,则使用单独的函数(init())来分配内存。

    如果您将在所有成员函数中检查内存是否已分配并检查NULL条件,则应在构造函数中分配内存。

  2. 异常处理(即try...catch)是更好的选择。

  3. 除了调用构造函数之外,“this”指针也被初始化。


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