Путаница в обработке исключений C++

Итак, в основном у меня есть этот простой код оболочки для внешней библиотеки C, и я новичок с точки зрения правильной обработки исключений.

Заранее: код показывает одну и ту же проблему два раза, но, возможно, для версии класса есть другое решение.

#include <some_c_lib>

void setup(){
    //some setup code

    //init function from the C library
    //with C-style return code for error handling
    if(!init()){ 
        //error: program should terminate
        //because error cannot be handled
    }

    //some setup code
}

class myclass{
    //some members

public:
    myclass(){
        //some construction code

        //create function of the C library
        //with C-style return code error handling
        if(!create()){
            //error: program should terminate
            //because error cannot be handled
        }
    }

    ~myclass(){
        //desturction code
    }
};

int main(){
    std::ostream log("log.txt");  //logfile as an example

    setup();

    myclass obj;

    while(everything_is_fine()){
        //main loop stuff
    }

}

Проблема в том, что я не знаю, как лучше завершить программу. Я не хочу ловить исключение в main. Это было бы бессмысленно и некрасиво, потому что исключение все равно нельзя обработать. Тем не менее, я хотел бы иметь какой-то механизм раскручивания стека. Если я просто exit запишу программу внутри if блоков, то файл журнала, например, не будет уничтожен должным образом. Я прав?

  • Закроется ли файл, если я брошу внутрь if, но нигде не предоставлю блок try-catch?

  • Как бороться с исключениями, возникающими в конструкторах?

  • Есть ли лучший способ справиться с этим типом проблемы?

Надеюсь стало понятно в чем моя проблема.

Спасибо за ответы, хорошего дня или вечера.


person bakkaa    schedule 18.11.2016    source источник


Ответы (2)


Это зависит, и вы действительно не дали достаточно информации. Вообще говоря, не существует абсолютного «лучшего» - это зависит от потребностей вашей программы, а не от подхода «один размер подходит всем».

Только в небольших тривиальных программах (например, таких, которые вы будете делать на занятиях, а не на рабочем месте) ошибка всегда требует немедленного завершения программы. Реальная потребность более размыта — в зависимости от того, что делает ваша программа, часто есть возможность восстановиться после ошибки и продолжить (либо в обычном режиме, либо в каком-то ухудшенном режиме). Также может быть предпочтительнее принять меры для предотвращения ошибки (например, обнаружить неверные данные и сделать что-то для восстановления, прежде чем выполнять операцию с неверными данными и вызывать состояние ошибки).

Однако, как правило, если в конструкторе возникает ошибка (и это неизбежно, и конструктор не может ничего сделать для восстановления после ее возникновения и т. д.), необходимо создать исключение. По сути, это сигнализирует о том, что вызывающая сторона (или какая-либо функция в стеке вызовов) должна предпринять действия по восстановлению. Если вызывающий объект не может восстановиться, результатом по умолчанию для создания исключения является завершение программы — после вызова деструкторов всех объектов, созданных локально (с автоматическим сохранением продолжительности) в стеке вызовов. Это завершает программу - если это необходимо - и выполняет очистку в процессе, пока деструкторы очищаются правильно (что является целью деструкторов).

В вашем коде создание исключения (в конечном итоге) вернет управление main(). Если исключение не перехвачено, программа завершится, но не раньше, чем log будет уничтожено, что вызовет его деструктор. Деструктор стандартных классов потока вывода обычно очищает поток и закрывает его должным образом. Если вам нужно сделать больше, чем это (например, другое действие восстановления перед завершением, после сброса потока), напишите main() в качестве функционального блока попытки.

Обычно не рекомендуется выполнять «частичное построение» в конструкторе — например, конструктор настраивает некоторые основы, но затем пользователю приходится вызывать другую функцию для «дальнейшей» инициализации. Такие методы — это возможность забыть об инициализации, что в основном означает, что последующий код использует объект, который не инициализирован должным образом. В C++ такие приемы в любом случае редко бывают необходимы — можно отложить создание объекта до тех пор, пока не будет доступна вся информация для его правильной инициализации (в конструкторе).

Вообще говоря, возврат кодов ошибок (например, функция с типом возврата, отличным от void, функция, которая принимает указатель/ссылку на объект, в котором хранится информация о состоянии) уместна в различных обстоятельствах. Ничто не заставляет вызывающую сторону проверять возвращаемое функцией значение. Таким образом, код возврата подходит, если условие ошибки можно безопасно игнорировать (например, если ваш код забывает проверить его) или если функция используется только в обстоятельствах, когда будет проверяться код возврата. Ничто не мешает вам написать код, который преобразует код возврата (скажем, из функции, написанной на C) в исключение. Проблема с кодами возврата заключается в том, что их можно забыть проверить, что может означать, что критические ошибки остаются необнаруженными/не сообщаемыми и вызывают ошибки в другом коде в программе.

person Peter    schedule 18.11.2016
comment
Спасибо за ваш ответ, это было действительно полезно. В случае с моей программой действительно нет способа восстановиться после ошибки, потому что ошибки исходят из библиотеки C, которую я использую, и, вероятно, вызваны несовместимым оборудованием или какими-то другими внешними вещами. На данный момент программа небольшая и тривиальная, но я хочу сделать ее основой для более крупных проектов. Поэтому я думаю, что очень важно с самого начала разобраться с обработкой ошибок. Я, конечно, понимаю, что нет лучшего решения для всех видов проблем, но я хочу получить представление о том, как справляться с ошибками. - person bakkaa; 19.11.2016
comment
Я правильно понял, что достаточно выдать ошибку в конструкторе и вообще не поймать ее, чтобы принудительно завершить программу? Я не нашел способа убедиться, что все очищается правильно с помощью этого метода. Я попытался вывести что-то на консоль из тестового деструктора, но ничего не отображалось. У вас есть способ, как я могу это проверить? - person bakkaa; 19.11.2016
comment
Если нет подходящего предложения catch (включая catch (...), вызывается terminate(). Действие по умолчанию terminate() заключается в вызове abort(), что приводит к выходу из программы без вызова деструкторов каких-либо объектов, которые существуют при ее вызове. Обычно лучше перехватывать исключения. в main(), а затем нормально завершить работу, чтобы обеспечить надлежащую очистку.Однако можно зарегистрировать обработчики завершения. - person Peter; 20.11.2016

Одно из решений — не писать свои конструкторы таким образом, чтобы они вызывали ошибки. Обычной практикой является использование конструкторов для установки переменных-членов и т. д., а затем наличие метода, такого как bool initialize(); который возвращает значение, основанное на том, может ли класс выполнить более сложную инициализацию без ошибок.

Вместо bool вы также можете возвращать другие значения или структуры для более информативных ошибок.

В конце концов, ваш файл журнала все еще может быть записан любым классом и должен содержать информацию об ошибках, если они возникнут.

person Stephen    schedule 18.11.2016
comment
Спасибо за Ваш ответ. Это не очень помогает мне с моей проблемой, но в любом случае это полезная информация для будущих проблем, которые могут возникнуть. Поэтому я дал вам голос, чтобы компенсировать один отрицательный голос, который у вас есть :) - person bakkaa; 19.11.2016