C ++: How to catch exceptions from constructors?

I have a class, name it A

, whose constructor takes some input arguments and can throw an exception if they are inconsistent to construct this object. In my code, main

I create an object of type A

like this:

A my_obj(arg1,arg2,arg3);

      

and use it. Obviously, if the constructor fails and throws an exception, program execution will be terminated after the "unhandled exception" error message is displayed.

I would, however, like to give the user more information in this case and tell him why it was thrown exception

. So, I need a way to catch

exclude.

To this end, one possibility is to enclose all the code, from the declaration my_obj

to the end of the program in a block try

and catch

then the exception:

try {
    A my_obj(arg1, arg2, arg3);
    // ... 
    // about 100 other lines of code being executed if my_obj is created properly 
}
catch (std::exception& e) {
    // print a user-friendly error message and exit
}

      

But it looks a little overkill. In particular, since no other exceptions are thrown in the remaining 100 lines. Is there any other other way to do this?

+3


source to share


5 answers


You can abstract the construction of an object into a function that catches the exception:

template<typename... Args>
A make_a(Args&&... args) {
    try {
        return A(std::forward(args)...);
    }
    catch (std::exception& e) {
        // print a user-friendly error message and exit
        ...
        std::exit(EXIT_FAILURE);
    }
}

// ... in the actual code:
A my_obj = make_a(arg1, arg2, arg3);

      



The example above takes advantage of the fact that your program crashes if the construct fails. If the requirement must continue, the function can return std::optional<A>

(or its boost equivalent if you don't have access to C ++ 17.)

0


source


If the constructor throws, you don't have an object. std::optional<>

is a type that means "We may not have an object here."

template <typename T, typename ... Args>
std::optional<T> try_make(Args&& ... args)
{ try {
    return make_optional(std::forward(args...));
} catch (...) {
    return {};
} }

      



Then

auto my_obj = try_make<A>(arg1,arg2,arg3);
if (my_obj) {
    // about 100 other lines of code being executed if my_obj is created properly
}

      

+2


source


One possibility would be to use a pointer (it is better to use a smart pointer like unique_ptr as shown below in the code). You leave it unique_ptr

blank, call the constructor in the try block, and move the pointer to unique_ptr

. After that, your other code is executed. Of course, you should check for a valid pointer with operator bool unique_ptr

in a simple if statement.
To simplify the use of my_obj

used link: A& my_obj_ref = *my_obj;

.

std::unique_ptr<A> my_obj;
try {
    my_obj = std::move(std::unique_ptr<A>(new A(arg1, arg2, arg3));
}
catch (std::exception& e) {
    // print a user-friendly error message and exit
}

if (my_obj) { // needed if your exception handling doesn't break out of the function
    A& my_obj_ref = *my_obj;

    // ... 
    // about 100 other lines of code being executed if my_obj is created properly
}

      

Remember that this way would allocate your object on the heap instead of the stack.

+1


source


You have several options here, depending on how you want the control to continue if the construct fails.


If you want to exit the function by throwing an exception, you don't need to do anything, you can resolve the construct exception A

.

If you want to exit, either by throwing another exception, or by doing some steps before resolving the construct exception A

, then use a factory function (possibly a lambda) that does those things, for example:

auto a_factory(T x, U y) -> A     // or use perfect forwarding
{
    try { return A(x, y);  }
    catch(...) {
         log("constructing A failed...");
         throw other_exception();
    } 
}

// ... 
     A my_obj = a_factory(x, y);        

      


If you want to exit by returning a value, you can still use the above method, but wrap the calling function with another function that catches the expected exceptions and returns a value.

Or you can use the technique optional

(below) or unique_ptr

(as described in other answers) but follow the instruction return

from the block catch

.


If you want to continue execution without a valid one A

, you can do:

std::optional<A> opt_my_obj;
try
{
    A temp(...args...);
    opt_my_obj.swap(temp);
} catch(...)
{
    // handling, you could return from the function here
}

// At this point you can test `if ( opt_my_obj )` to branch the flow.
// When you're at a point where you have verified the object exists, you 
// can enable normal object syntax by writing:

A& my_obj = *opt_my_obj;

      


If you have multiple objects in your function that need this consideration, I would like to suggest a version of having the entire function wrapped in try ... catch that can handle all different exceptions.

0


source


I try to keep it simple: throw a humanoid message. This strategy works well when there is no choice, and usually not. However, there is a catch, you want the exception handling to be robust enough, so I wrap the message inside a trim std::array<char,4096>

if necessary and remember the null terminator (I know this can lead to the stack being removed, but it should be fine if we're not in recursive function) and throw that.

Example:

try
    {
    Options opts(argv);
    SomeResource resource(opts.someParameter());
    //...More actions that could throw
    }
catch(const std::array<char,4096>& errmessage) //Or rather some other type that contains the message.
    {
    fprintf(stderr,"Error: %s\n",errmessage.data());
    return -1; //Or any non-zero value
    }
return 0;

      

Pros:

  • Quickly implement new constructors for new classes as there is only one exception class that will work for everything
  • You will receive any system messages directly from the source

Minuses:

  • Lack of context: the message should say something like "Unable to open file foo: No such file or directory." Without telling the user what the root cause of the exception is. This problem is inherited from the exception model and cannot be resolved without handling exceptions as glorified error codes.

  • If you want to branch out on the content of the exceptions, you should parse the message, but I find this rarely necessary. Possibly in a compiler context, but this will still print this message foo:54:1: Error: bar is not a baz

    .

0


source







All Articles