Путаница в практике программирования на C ++ с обработкой исключений - PullRequest
2 голосов
/ 08 марта 2012

У меня есть код 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, и выводится следующий вывод без любого сбоя приложения.

DtoR виджета называется

В улове BLOCK

OK имеет значение NULL

Ошибка при выделении памяти.

Нажмите любую клавишу для продолжения.,.

Мой вопрос заключается в том, почему такая разница в одном случае, когда мы не инициализируем указатель 'pw' на NULL, а в другом случае мы устанавливаем его на NULL.Почему деструктор вызывается в одном случае и почему он не вызывается в другом.

Примечание. Цель этого кода - вызвать исключение bad_alloc.

Ответы [ 6 ]

6 голосов
/ 08 марта 2012

Если вы не установите pw на NULL, то оставите его неинициализированным. Затем, когда оператор new внутри блока try генерирует исключение, он никогда не возвращается, и вы попадаете в блок catch. Поскольку new никогда не возвращается, pw все равно не будет инициализирован, и вы передадите случайный адрес в delete. Это дает вам неопределенное поведение.

4 голосов
/ 08 марта 2012

В вашем блоке catch у вас есть:

if(pw->ok == NULL)

На данный момент pw равно NULL (или мусор, если вы его не инициализировали).pw-ok пытается разыменовать его, давая неопределенное поведение (в данном случае сбой).

Если вы не инициализировали его, то delete pw завершится сбоем перед выводом сообщения «catch»;скорее всего, он напечатает сообщение «Dtor» перед сбоем, но нет никакой гарантии, так как вы твердо находитесь в сфере неопределенного поведения.

Если вы его инициализировали, то delete pw не требуется, нобезвредны;удаление нулевого указателя определено, чтобы ничего не делать.Таким образом, в этом случае вы не будете аварийно завершать работу, пока не прекратите его разыскивать.

В любом случае у вас будет нефиксированная утечка памяти - первое выделение ok = new int[1024] будет выполнено успешно, но вы потеряли единственный указатель на него,Вот почему вы всегда должны управлять динамической памятью, используя умные указатели, контейнеры и другие RAII методы.

1 голос
/ 08 марта 2012

вы собираетесь создать исключение bad_alloc. Но у вас есть больше необработанных исключений! Нельзя сначала удалить pw, а затем использовать его указатель!

           delete pw;
           if(pw->ok == NULL)
0 голосов
/ 08 марта 2012
  1. Зачем вам звонить pw->ok после удаления pw?Его уже нет.
  2. Ваш конструктор должен инициализировать член

Widget():ok(NULL), prob(NULL) {
...
}

, потому что если Widget () завершится неудачно, вы не знаете, какая переменная-член инициализирована, а какая нет, какаяможет вызвать проблемы в вашем деструкторе.

  1. Поскольку вы выделяете int[], вам нужно delete[], а не просто delete в вашем деструкторе.
0 голосов
/ 08 марта 2012

Хорошо инициализировать pw до NULL, но при удалении вы должны сначала проверить, не является ли pw нулевым. Что-то вроде:

if (pw) delete pw;

Кроме того, если pw равно NULL, вы не можете ссылаться на его членов.

0 голосов
/ 08 марта 2012

Вы видите сбой приложения, когда для pw установлено значение NULL из-за строки

if (pw->ok == NULL)

Вы разыменовываете NULL, что вызывает сбой.В другом случае вы удаляете мусор, что приведет к неопределенному поведению.

Кроме того, вы не должны использовать указатель после вызова удаления для него.Это может вызвать все виды странного поведения.

Чтобы объяснить больше того, что происходит.Ваш конструктор 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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...