Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I / Should I use std::exception's for regular error handling?

I'm going to start this new project in C++ and am thinking about an un-painful way to do error handling. Now, I'm not going to start out throwing and catching exceptions, and will quite possibly never throw exceptions at all, but I was thinking - even for regular error handling, why roll my own / copy-paste a class for describing errors/status, when I could just use std::exception and its child classes (Or perhaps an std::optional<std::exception>)?

using Status = std::optional<std::exception>;
Status somethingThatMayFail(int x);

Is anybody/any project working this way? Is it a ridiculous idea or just a bit creaky?

like image 945
einpoklum Avatar asked Apr 28 '15 21:04

einpoklum


People also ask

What block allows you to handle errors?

The try block lets you test a block of code for errors. The except block lets you handle the error. The else block lets you execute code when there is no error. The finally block lets you execute code, regardless of the result of the try- and except blocks.

Which function is used for error handling?

ferror() function is contained in stdio. This function basically checks for error in the file stream. It returns zero value if there is no error or else, it returns a positive non-zero value in case of error. File pointer stream is passed as an argument to the function.

Which class is used for handling errors?

All exception and error types are subclasses of class Throwable, which is the base class of the hierarchy.


2 Answers

I don't think you should construct exceptions unless you actually intend to throw them. I would recommend a bool or enum return type. The intent will be much clearer to someone reading your code, and they will be faster. However, if you construct an exception, someone else will come along and think they can throw the exception and cause the whole system to crash.

C++ exceptions play an important role in resource management, triggering destructors and all that (RAII). Using them any other way is going to hurt performance and (more importantly) confuse the holy heck out of anyone trying to maintain the code, later.

You can, however, do what you want with a status reporting class that does NOT have anything to do with std::exception. People do way too much for "faster" code when they don't need to. If a status enum isn't good enough, and you need to return more info, then a status reporting class will work. If it makes the code easier to read, then go for it.

Just don't call it an exception unless you actually throw it.

like image 138
Kenny Ostrom Avatar answered Sep 21 '22 18:09

Kenny Ostrom


I think that performance alone might prove problematic. Consider the following code:

#include <iostream>
#include <chrono>
#include <ctime>
#include <stdexcept>

#include <boost/optional.hpp>   


int return_code_foo(int i)   
{
    if(i < 10)  
        return -1;
    return 0;
} 


std::logic_error return_exception_foo(int i)   
{
    if(i < 10)  
        return std::logic_error("error");
    return std::logic_error("");
} 


boost::optional<std::logic_error> return_optional_foo(int i)   
{
    if(i < 10)  
        return boost::optional<std::logic_error>(std::logic_error("error"));
    return boost::optional<std::logic_error>();
} 


void exception_foo(int i)   
{
    if(i < 10)  
        throw std::logic_error("error");
} 


int main()
{
    std::chrono::time_point<std::chrono::system_clock> start, end;

    start = std::chrono::system_clock::now();
    for(size_t i = 11; i < 9999999; ++i)
        return_code_foo(i);
    end = std::chrono::system_clock::now();
    std::cout << "code elapsed time: " << (end - start).count() << "s\n";

    start = std::chrono::system_clock::now();
    for(size_t i = 11; i < 9999999; ++i)
        return_exception_foo(i);
    end = std::chrono::system_clock::now();
    std::cout << "exception elapsed time: " << (end - start).count() << "s\n";

    start = std::chrono::system_clock::now();
    for(size_t i = 11; i < 9999999; ++i)
        return_optional_foo(i);
    end = std::chrono::system_clock::now();
    std::cout << "optional elapsed time: " << (end - start).count() << "s\n";

    start = std::chrono::system_clock::now();
    for(size_t i = 11; i < 9999999; ++i)
        exception_foo(i);
    end = std::chrono::system_clock::now();
    std::cout << "exception elapsed time: " << (end - start).count() << "s\n";

    return 0;
}

On my CentOS, using gcc 4.7, it timed at:

[amit@amit tmp]$ ./a.out 
code elapsed time: 39893s
exception elapsed time: 466762s
optional elapsed time: 215282s
exception elapsed time: 38436s

in vanilla settings, and:

[amit@amit tmp]$ ./a.out 
code elapsed time: 0s
exception elapsed time: 238985s
optional elapsed time: 33595s
exception elapsed time: 24350

at -O2 settings.

P.S. I personally would use exceptions/stack-unwinding due to a belief that it is a fundamental part of C+, possibly as @vsoftco said above.

like image 21
Ami Tavory Avatar answered Sep 21 '22 18:09

Ami Tavory