我目前正在开发一个C ++项目,其中经常出现动态数组。 我想知道,使用new-operator初始化动态数组的正确方法是什么?我的一位同事告诉我,在构造函数中使用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,或者更好的是创建一个额外的init() - State-Array的方法以及为什么?
2。)检查新功能是否成功的最佳方法是什么:
if (!ptr) std::cerr << "new failed."
或
try { /*new*/ } catch (std::bad_alloc) { /*handling*/ }
3。)好了它的三个问题; o)在引擎盖下,new会做某种
ptr = (Struct *)malloc(N*sizeof(Struct));
然后调用构造函数,对吧?
答案 0 :(得分:7)
你应该让std::bad_alloc
宣传 - 无论如何你可能无法做任何合理的事。
首先,从构造函数is the only reliable way to signal a problem抛出一个异常 - 如果没有异常则意味着对象是完全构造的。因此单独捕捉std::bad_alloc
无助于防止其他可能的例外。
那么你能做些什么来“处理”它,以便其他代码知道并且可以做出适当的反应?
正确使用例外 - let them propagate to the site where they can be reasonably handled。
答案 1 :(得分:5)
构造函数的整个目的是构造一个对象。这包括初始化。当构造函数完成执行时,该对象应该可以使用了。这意味着构造函数应该执行任何必要的初始化。
你的朋友建议导致无法维护和容易出错的代码,并违背每一个优秀的C ++实践。他错了。
对于动态数组,请改用std::vector
。要初始化它,只需将参数传递给构造函数:
std::vector<int>(10, 20)
将创建一个10个整数的向量,所有这些整数都初始化为值20。
答案 2 :(得分:1)
不是一个完整的答案,只是我的2美分:
1:我会在构造函数中使用new,尽管对于动态数组,STL是可行的方法。
2:new的正常错误处理是引发异常,因此检查返回的指针是没用的。
3:不要忘记使用新操作符来使故事更有趣。
答案 3 :(得分: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),在这种情况下你释放处理程序中的其他资源,但是你重新抛出异常,因为你应该让它传播。
答案 4 :(得分:0)
你的朋友有点对。但是,通常的做法是在构造函数中执行内存预留,并在析构函数中执行解除分配。
构造函数中可能失败的方法的问题如下:构造函数没有传统的方法来通知调用者问题。调用者希望得到一个没有任何损坏的对象实例。
此问题的解决方案是抛出/传播异常。异常将始终通过并且可以由调用者处理。
答案 5 :(得分:0)
如果您正在寻找返回类型,即如果函数必须返回状态,则使用单独的函数(init())来分配内存。
如果您要检查是否通过检查所有成员函数中的NULL条件来检查内存是否已分配,则在构造函数本身中分配内存。
异常处理(即try ... catch)是更好的选择。
除了调用构造函数之外,“this”指针也被初始化。