【问题标题】:Confusion on C++ programming practise with exception handlingC++ 编程实践与异常处理的混淆
【发布时间】:2012-03-25 09:23:11
【问题描述】:

我有一个下面提到的 C++ 代码:

#include<iostream>
#include<stdexcept>

const long MAX = 10240000;

class Widget{
      public:
             Widget(){
                      ok = new int [1024];
                      prob = new int [100*MAX];
             }
             ~Widget(){
                       std::cout<<"\nDtoR of Widget is called\n";
                       delete ok; ok = NULL;
                       delete prob; prob = NULL;
             }
             //keeping below public: Intentionally
              int* ok;
              int* prob;
};


void func(){
    Widget* pw = NULL;  // <<<<--------------- Issue in Here
    try{
        pw = new Widget;
    }
    catch(...){
               delete pw;
               std::cout<<"\nIn catch BLOCK\n";
               if(pw->ok == NULL){
                      std::cout<<"\n OK is NULL\n";
               }
               throw;
    }
    delete pw;
}

int main(){
    try{
          func();
    }
    catch(std::bad_alloc&){
                     std::cout<<"\nError allocating memory."<<std::endl;
    }

    std::cout<<std::endl;
    system("pause");
    return 0;
}

现在在函数 func() 中,我看到两种不同的行为,这取决于我是否没有将指针“pw”设置为 NULL 以及是否将“pw”指针设置为 NULL(如上面的代码)。我的印象是,首先将指针设置为 NULL 然后初始化它是“好”的做法。但是当我将它初始化为 NULL 时,输出只显示“In catch BLOCK”,然后应用程序崩溃。但是如果我没有将 pw 指针设置为 NULL,那么我会看到 pw 的析构函数被调用,并且以下输出显示为没有任何应用程序崩溃。

Widget的DtoR被调用

在捕获块中

OK 为 NULL

分配内存时出错。

按任意键继续。 . .

我的问题是为什么在一种情况下我们没有将“pw”指针初始化为 NULL,而在另一种情况下我们将其设置为 NULL。为什么在一种情况下调用析构函数而在另一种情况下不调用它。

注意:此代码的目的是抛出 bad_alloc 异常。

【问题讨论】:

    标签: c++ exception-handling


    【解决方案1】:

    当您将 pw 设置为 NULL 时,您会看到应用程序崩溃,因为该行

    if (pw->ok == NULL)
    

    您正在取消引用导致崩溃的 NULL。在另一种情况下,您正在删除垃圾,这会给您带来未定义的行为。

    另外,你不应该在调用 delete 后使用指针。这可能会导致各种奇怪的行为。

    解释更多正在发生的事情。您的 Widget 构造函数正在引发分配异常。在这种情况下,ok 的分配很可能已完成,但prob 的分配失败。您的构造函数永远不会完成,泄漏分配给ok 变量的内存。如果要确保清理内存,则应在构造函数中添加 try catch。

    Widget() : ok(NULL), prob(NULL)
    {
        try
        {
            ok = new int [1024];
            prob = new int [100*MAX];
        }
        catch(...)
        {
            std::cout << "\nCtor of Widget threw exception\n";
            delete [] ok;
            delete [] prob;
            throw;
        }
    }
    

    【讨论】:

    • 别忘了delete [] 而不是delete
    • 很好,我通常会在代码审查期间调用其他开发人员:)
    • 也应该重新扔。不过,我认为最好的办法是使用真正的容器,例如std::vectorstd::array 在这种情况下也可以使用,看来。
    • 抛出是我的初衷,只是在等待我正在处理的项目遇到断点时匆忙输入。我同意,这里不需要对数组使用 new ,可以使用向量并保留空间(或者如果您要执行 memcpy 或类似的操作,则使用 resize )。我只是想展示如何清理部分构造的类(不过我今天显然无法做到这一点)
    【解决方案2】:

    您打算使用 bad_alloc 异常。但是您有更多未处理的异常! 不能先删除 pw 再使用它的指针!

               delete pw;
               if(pw->ok == NULL)
    

    【讨论】:

      【解决方案3】:

      在你的 catch 块中,你有:

      if(pw->ok == NULL)
      

      此时,pwNULL(或者垃圾,如果你没有初始化它)。 pw-ok 试图取消引用它,给出未定义的行为(在这种情况下是崩溃)。

      如果你没有初始化它,那么delete pw会在打印“catch”消息之前崩溃;最有可能的是,它会在崩溃之前打印“Dtor”消息,但无法保证,因为您正处于未定义行为的领域。

      如果你确实初始化了它,那么delete pw 是不必要但无害的;删除空指针被定义为什么都不做。因此,在这种情况下,除非您取消引用它,否则您不会崩溃。

      无论如何,你有一个无法修复的内存泄漏——第一次分配ok = new int[1024] 会成功,但是你丢失了唯一指向它的指针。这就是为什么您应该始终使用智能指针、容器和其他RAII 技术来管理动态内存。

      【讨论】:

      • 感谢您的解释。我希望我能选择两个答案。
      【解决方案4】:
      1. 为什么在删除pw 后还要调用pw-&gt;ok?它已经消失了。
      2. 你的构造函数应该有成员初始化
      
      Widget():ok(NULL), prob(NULL) {
      ...
      }
      

      因为如果 Widget() 失败,您不知道哪个成员变量已初始化,哪个未初始化,这会导致您的析构函数出现问题。

      1. 由于您分配了int[],因此您需要在析构函数中使用delete[] 而不仅仅是delete

      【讨论】:

      • 如果构造函数抛出,析构函数不会被调用。因此,即使有您的建议,它也可能会泄漏。正确的解决方案是使用适当的容器而不是原生数组。
      【解决方案5】:

      pw初始化成NULL就好了,但是删除的时候要先检查pw是否不为空。比如:

      if (pw) delete pw;
      

      另外,如果pwNULL,则不能引用其成员。

      【讨论】:

      • 但是,更好地查看您的代码,catch 中的 delete pw 似乎不合适,因为如果您的程序运行该代码,您将拥有一个 NULL pw
      • 不过,我应该提一下,您的第二点是正确的。
      【解决方案6】:

      如果您没有将pw 设置为NULL,那么您将使其保持未初始化状态。然后,当“try”块内的new 运算符抛出异常时,它永远不会返回,您将进入catch 块。由于new 从未返回,pw 仍然不会被初始化,您将向delete 传递一个随机地址。这会给你一个未定义的行为。

      【讨论】:

      • 别忘了提他在删除后做if(pw->ok)
      • 感谢 vlad 解释此行为。 PlasmaHH:我同意。我的错。我只是跳过它。
      • 您可能还想补充一点,在catch 块中调用delete pw; 是没有意义的,因为如果pw = new Widget; 抛出,则保证不会分配内存。
      • @BrianNeal:我想过这个问题。但我相信在现实生活中,一些代码很可能会遵循 try-catch 块中的 new 调用。在这种情况下,如果在堆栈展开时不使用智能指针删除,则delete 将在catch 块中需要,或者在没有错误的情况下在它之后。
      • 我同意,如果try 中还有其他代码,那么您可能需要catch 中的delete。使用智能指针的另一个原因!
      猜你喜欢
      • 2011-07-29
      • 1970-01-01
      • 1970-01-01
      • 2016-12-15
      • 2011-04-10
      • 1970-01-01
      • 2018-01-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多