有人可以准确解释如果在堆上分配对象数组的过程中抛出异常会发生什么吗?
我定义了一个类 foo 如下:
class foo {
private:
static int objcnt;
public:
foo() {
if(objcnt==8)
throw outOfMemory("No more space!");
else
objcnt++;
}
class outOfMemory {
public:
outOfMemory(char* msg) { cout << msg << endl;}
};
~foo() { cout << "Deleting foo." << endl; objcnt--;}
};
int foo::objcnt = 0;
这是主要函数:
int main() {
try {
foo* p = new foo[3];
cout << "p in try " << p << endl;
foo* q = new foo[7];
}catch(foo::outOfMemory& o) {
cout << "Out-of-memory Exception Caught." << endl;
}
}
很明显,“foo* q = new foo[7];”这一行只成功创建了5个对象,第6个对象抛出Out-of-memory异常。但事实证明,只有 5 个析构函数调用,并且 p 指向的位置存储的 3 个对象的数组没有调用析构函数。所以我想知道为什么?为什么程序只调用这 5 个对象的析构函数?
I defined a class foo as follows:
class foo {
private:
static int objcnt;
public:
foo() {
if(objcnt==8)
throw outOfMemory("No more space!");
else
objcnt++;
}
class outOfMemory {
public:
outOfMemory(char* msg) { cout << msg << endl;}
};
~foo() { cout << "Deleting foo." << endl; objcnt--;}
};
int foo::objcnt = 0;
And here's the main function:
int main() {
try {
foo* p = new foo[3];
cout << "p in try " << p << endl;
foo* q = new foo[7];
}catch(foo::outOfMemory& o) {
cout << "Out-of-memory Exception Caught." << endl;
}
}
It is obvious that the line "foo* q = new foo[7];" only creates 5 objects successfully, and on the 6th object an Out-of-memory exception is thrown. But it turns out that there's only 5 destructor calls, and destrcutor is not called for the array of 3 objects stored at the position p points to. So I am wondering why? How come the program only calls the destructor for those 5 objects?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
“原子”C++ 分配和构造函数是正确的并且是异常安全的: If
new T
;抛出,没有任何泄漏,并且如果 new T[N] 在此过程中的任何地方抛出,则已经构造的所有内容都会被销毁。所以没什么好担心的。现在题外话:
您始终必须担心的是在任何单一责任单元中使用多个
new
表达式。基本上,您必须将任何 new 表达式视为烫手山芋,需要由完整构造的、负责任的监护对象吸收。将
new
和new[]
严格视为库构建块:您永远不会在高级用户代码中使用它们(也许除了单个new< /code> 在构造函数中),并且仅在库类中。
也就是说:
另一方面:标准库容器实现了类似的行为:如果您说
resize(N)
(增长),并且在任何构造期间发生异常,那么所有已构造的元素都会被销毁。也就是说,resize(N)
要么将容器增大到指定的大小,要么根本不增大。 (例如,在 GCC 4.6 中,请参阅bits/vector.tcc
中_M_fill_insert()
的实现,了解异常检查范围构造的库版本。)The "atomic" C++ allocation and construction functions are correct and exception-safe: If
new T
; throws, nothing leaks, and ifnew T[N]
throws anywhere along the way, everything that's already been constructed is destroyed. So nothing to worry there.Now a digression:
What you always must worry about is using more than one
new
expression in any single unit of responsibility. Basically, you have to consider anynew
expression as a hot potato that needs to be absorbed by a fully-constructed, responsible guardian object.Consider
new
andnew[]
strictly as library building blocks: You will never use them in high-level user code (perhaps with the exception of a singlenew
in a constructor), and only inside library classes.To wit:
As another aside: The standard library containers implement a similar behaviour: If you say
resize(N)
(growing), and an exception occurs during any of the constructions, then all of the already-constructed elements are destroyed. That is,resize(N)
either grows the container to the specified size or not at all. (E.g. in GCC 4.6, see the implementation of_M_fill_insert()
inbits/vector.tcc
for a library version of exception-checked range construction.)仅针对完全构造的对象调用析构函数 - 这些对象的构造函数正常完成。仅当
new[]
正在进行时引发异常时,才会自动发生这种情况。因此,在您的示例中,析构函数将为在q = new foo[7]
运行期间完全构造的五个对象运行。由于
p
指向的数组的new[]
已成功完成,因此该数组现在已由您的代码处理,并且 C++ 运行时不再关心它 - 没有析构函数除非您执行delete[] p
,否则将运行。Destructors are only called for the fully constructed objects - those are objects whose constructors completed normally. That only happens automatically if an exception is thrown while
new[]
is in progress. So in your example the destructors will be run for five objects fully constructed duringq = new foo[7]
running.Since
new[]
for the array thatp
points to completed successfully that array is now handled to your code and the C++ runtime doesn't care of it anymore - no destructors will be run unless you dodelete[] p
.当您在堆上声明数组时,您会得到预期的行为:
在您的代码中,只有指针是本地自动变量。当堆栈展开时,指针没有任何关联的清理。正如其他人指出的那样,这就是为什么 C++ 代码中通常没有 RAW 指针,它们通常包装在使用构造函数/析构函数来控制其生命周期的类对象中(智能指针/容器)。
作为旁注。使用 std::vector 通常比使用原始数组更好(在 C++11 中,如果您有固定大小的数组,则 std::array 也很有用)。这是因为堆栈的大小有限,而这些对象将大量数据放入堆中。这些类对象提供的额外方法使它们在代码的其余部分中更容易处理,并且如果您绝对必须有一个旧式数组指针来传递给 C 函数,那么它们很容易获得。
You get the behavior you expect when you declare the arrays on the heap:
In your code only the pointers were local automatic variables. Pointers don't have any associated cleanup when the stack is unwound. As others have pointed out this is why you generally do not have RAW pointers in C++ code they are usually wrapped inside a class object that uses the constructor/destructor to control their lifespan (smart pointer/container).
As a side note. It is usually better to use std::vector than raw arrays (In C++11 std::array is also useful if you have a fixed size array). This is because the stack has a limited size and these object puts the bulk of the data in the heap. The extra methods provided by these class objects make them much nicer to handle in the rest of your code and if you absolutely must have an old style array pointer to pass to a C function they are easy to obtain.