Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does noexcept apply to exceptions propagating from initializer lists

Lets say I have a constructor like so

Something(SomethingElse) noexcept : member{SomethingElse, something_that_might_throw()} {
    ...
}

Is a noexcept okay in this case if the construction of member can throw? In the above example the member variable member is of a type I do not know.

On a side note: Are there other edge cases one needs to worry about when using noexcept?

like image 552
Curious Avatar asked Aug 07 '16 20:08

Curious


3 Answers

#UPDATE:(Based on your edit): The original answer applies to everything within the block-scope of the function (constructors inclusive, including constructor-initialization-list). Why don't you try the code and see. :-)


#Original Answer

Something(SomethingElse) : member{SomethingElse} noexcept {
    ...
}

Your code would not compile that way. noexcept comes before the colon :

Something(SomethingElse) noexcept : member{SomethingElse} {
    ...
}

To your question:

Is a noexcept okay in this case if the member class constructor can throw?

No, it's not okay. The noexcept specifier is a promise that an exception will not leave that function or constructor. Should it leave, the runtime will terminate your program.

If a search for a matching exception handler leaves a function marked noexcept or noexcept(true), std::terminate is called immediately.

This applies to the constructor.


Despite the try..catch block, the code below gets terminated:

struct Base {
    Base(){}
    Base(int){ throw int(5); }
};

struct Derived : public Base {
    Derived(int a) noexcept : Base(a) {}
};

int main()
{
    try{
        Derived d(45);
    } catch(...) {}
    return 0;
}

Output:

terminate called after throwing an instance of 'int'
bash: line 7:  7454 Aborted                 (core dumped) ./a.out

See it Live on Coliru

But if you remove the noexcept specification, you wouldn't abruptly end your program, exception handling will continue normally. :-).

I'll leave you to think of the consequences if you do this kind of thing in production or even a large code base with many contributors. Do not use noexcept if you are unsure of the exception guarantees of all the statements in your function/constructor block


like image 162
WhiZTiM Avatar answered Nov 18 '22 16:11

WhiZTiM


Yes, a noexcept on a constructor applies to base class/member construction.

If you need to deal with such a case, you probably want to use the little-known (and rarely used) function try block. The syntax looks something like this:

#include <iostream>

class bad_initializer {};

int do_throw() { 
    throw bad_initializer();
    return 1;
}

class something { 
    int member;
public:
    something() noexcept
        try : member{do_throw()} // <-- initializer list
        {
            // ctor body goes here
        }
        catch(bad_initializer const &) { 
            std::cerr << "initialization threw\n";
        }
};

int main() { 
    something s;
}

Now, the bad news: since you have a member that wasn't constructed, the catch block really has only a few options. Normal processing can't continue--an exception happened in constructing the object, which means the object can't ever finish construction. By the time you catch the exception, there's nothing you can do about that.

If it weren't noexcept, it could catch the exception, then do some processing (release any resources it did acquire successfully) then either rethrow the exception it caught, or throw a different exception (one that better reflected its inability to be constructed).

In this case, your meaningful choices are even more restricted: you can call terminate directly, or you can throw an exception, which will call terminate indirectly. About all the whole try/catch thing has done in this case is give you a chance to do a little processing before terminate gets called.

like image 24
Jerry Coffin Avatar answered Nov 18 '22 16:11

Jerry Coffin


In general it's not okay, but in certain circumstances it may be

noexcept is a promise, similar to the const qualifier of member functions, but unlike const it's not enforced at compile time (the compiler will not issue an error and perhaps not even a warning, if a noexcept function may actually throw, but see below). Thus, you can declare a function noexcept even if a compiler-generated check would find that this function may throw and the code will compile (otherwise your question would be impossible).

However, that defeats the purpose of noexcept. The main purpose is to indicate (1) to the programmer that they can use a certain functionality without worrying about exception safety, and (2) to the compiler that it does not need to add code for stack unwinding. Instead, if an exception is thrown in a noexcept function, the run-time will invoke std::terminate(), as required by the standard.

So, in general your constructor should only be noexcept, if all its functionality, including the construction of bases and members is too.

However, there is an exception. If you know that some method will never throw given any input possible in the particular situation, even if it may throw under other circumstances and hence not be noexcept, you can call this method in a noexcept situation. For example,

// requires non-negative x, will throw std::runtime_error for negative x
int my_func(std::int64_t x);

struct my_struct {
  std::int64_t q;
  my_struct(int x) noexcept : q(std::int64_t(x)*std::int64_t(x)) {}
  int member() const noexcept { return my_func(q); }  // fine: q>=0
};

In the situation of your post, this means if member(somethingelse_type) is not noexcept, but you know (from conditions outside the code snipped in your post) that no exception will be thrown for the particular argument provided in this circumstance, you may declare Something(SomethingElse) noexcept.

like image 1
Walter Avatar answered Nov 18 '22 17:11

Walter