有人可以准确解释如果在堆上分配对象数组的过程中抛出异常会发生什么吗?

发布于 2024-12-16 16:16:21 字数 892 浏览 1 评论 0原文

我定义了一个类 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

懒的傷心 2024-12-23 16:16:21

“原子”C++ 分配和构造函数是正确的并且是异常安全的: If new T;抛出,没有任何泄漏,并且如果 new T[N] 在此过程中的任何地方抛出,则已经构造的所有内容都会被销毁。所以没什么好担心的。

现在题外话:

始终必须担心的是在任何单一责任单元中使用多个new表达式。基本上,您必须将任何 new 表达式视为烫手山芋,需要由完整构造的、负责任的监护对象吸收。

newnew[] 严格视为库构建块:您永远不会在高级用户代码中使用它们(也许除了单个 new< /code> 在构造函数中),并且仅在库类中。

也就是说:

// BAD:
A * p = new A;
B * q = new B;  // Ouch -- *p may leak if this throws!

// Good:
std::unique_ptr<A> p(new A);
std::unique_ptr<B> q(new B); // who cares if this throws
std::unique_ptr<C[3]> r(new C[3]); // ditto

另一方面:标准库容器实现了类似的行为:如果您说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 if new 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 any new expression as a hot potato that needs to be absorbed by a fully-constructed, responsible guardian object.

Consider new and new[] strictly as library building blocks: You will never use them in high-level user code (perhaps with the exception of a single new in a constructor), and only inside library classes.

To wit:

// BAD:
A * p = new A;
B * q = new B;  // Ouch -- *p may leak if this throws!

// Good:
std::unique_ptr<A> p(new A);
std::unique_ptr<B> q(new B); // who cares if this throws
std::unique_ptr<C[3]> r(new C[3]); // ditto

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() in bits/vector.tcc for a library version of exception-checked range construction.)

少女净妖师 2024-12-23 16:16:21

仅针对完全构造的对象调用析构函数 - 这些对象的构造函数正常完成。仅当 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 during q = new foo[7] running.

Since new[] for the array that p 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 do delete[] p.

晚风撩人 2024-12-23 16:16:21

当您在堆上声明数组时,您会得到预期的行为:

int main()
{
    try
    {
        foo   p[3];
        cout << "p in try " << p << endl;
        foo   q[7];
    }
    catch(foo::outOfMemory& o)
    {
       cout << "Out-of-memory Exception Caught." << endl;
    }
}

在您的代码中,只有指针是本地自动变量。当堆栈展开时,指针没有任何关联的清理。正如其他人指出的那样,这就是为什么 C++ 代码中通常没有 RAW 指针,它们通常包装在使用构造函数/析构函数来控制其生命周期的类对象中(智能指针/容器)。

作为旁注。使用 std::vector 通常比使用原始数组更好(在 C++11 中,如果您有固定大小的数组,则 std::array 也很有用)。这是因为堆栈的大小有限,而这些对象将大量数据放入堆中。这些类对象提供的额外方法使它们在代码的其余部分中更容易处理,并且如果您绝对必须有一个旧式数组指针来传递给 C 函数,那么它们很容易获得。

int main()
{
    try
    {
        std::vector<foo>     p(3);
        cout << "p in try " << p << endl;
        std::vector<foo>     q(7);

        // Now you can pass p/q to function much easier.

    }
    catch(foo::outOfMemory& o)
    {
       cout << "Out-of-memory Exception Caught." << endl;
    }
}

You get the behavior you expect when you declare the arrays on the heap:

int main()
{
    try
    {
        foo   p[3];
        cout << "p in try " << p << endl;
        foo   q[7];
    }
    catch(foo::outOfMemory& o)
    {
       cout << "Out-of-memory Exception Caught." << endl;
    }
}

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.

int main()
{
    try
    {
        std::vector<foo>     p(3);
        cout << "p in try " << p << endl;
        std::vector<foo>     q(7);

        // Now you can pass p/q to function much easier.

    }
    catch(foo::outOfMemory& o)
    {
       cout << "Out-of-memory Exception Caught." << endl;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文