Reputation: 2562
I am trying to understand "constructor function try block"; in "C++ Primer, 5th edition", chapter 18:
template <typename T> Blob<T>::Blob(std::initializer_list<T> il) try : data(std::make_shared<std::vector<T>>(il)) { /* empty body */ } catch(const std::bad_alloc &e) { handle_out_of_memory(e); }
Notice that the keyword try appears before the colon that begins the constructor initializer list and before the curly brace that forms the (in this case empty) constructor function body. The catch associated with this try can be used to handle exceptions thrown either from within the member initialization list or from within the constructor body.
It is worth noting that an exception can happen while initializing the constructor's parameters. Such exceptions are not part of the function try block. The function try block handles only exceptions that occur once the constructor begins executing. As with any other function call, if an exception occurs during parameter initialization, that exception is part of the calling expression and is handled in the caller’s context.
Note The only way for a constructor to handle an exception from a constructor initializer is to write the constructor as a function try block.
I didn't understand the second and third paragraphs; it looks to me like it is contradictory, because it is said that to handle exceptions from a constructor-init list we use ctor-function-try block, and from the other side it said that "it's worth noting that an exception... such exceptions are not a part from function-try-block...".
And for practice, I've tried this:
struct Employee{
Employee(int x) try :
age_(x <= 0 ? throw std::runtime_error("Invalid age: ") : x){
std::cout << "Employee(int)\n";
}
catch(std::exception const& e){
std::cout << e.what() << '\n';
}
catch(...){
std::cout << "Unhandled exception!\n";
}
int age_;
};
int main(){
//try{
Employee e(-4);
//}
//catch(std::exception const& e){
// std::cout << e.what() << '\n';
//}
std::cout << '\n';
}
And now the exception in not handled and std::terminate
is called?!
If I un-comment the try-catch
block in main()
, it works fine! So I think the book is correct but I don't understand it.
Can someone help me to de-mystify the correct usage of ctor-func-try-block? And, what's the point in my Employee
constructor to use function-try-block as long as an exception thrown from there must be handled from the caller point?
Upvotes: 2
Views: 717
Reputation: 5510
The catch
blocks in the function-scoped try-catch
in the constructor must themselves throw because the object wasn't constructed correctly, and there is no other way to signal a failure to create the object other than throwing an exception.
However, what makes the function-scoped try-catch
useful nevertheless is that you don't have to re-throw the same exception, you can for example do the following:
struct Employee{
explicit Employee(int x) try
: age_(x <= 0
? throw std::runtime_error("Invalid age: " + std::to_string(x))
: x)
{
std::puts("Employee(int)");
}
catch(std::exception const& e)
{
std::cout << e.what() << '\n';
// throw a totally different exception
throw std::system_error{
std::make_error_code(std::errc::argument_out_of_domain)};
}
catch(...)
{
std::puts("Unhandled exception!");
// will automatically re-throw
}
int age_;
};
int main()
{
try
{
Employee e(-4);
}
catch(std::system_error const& e)
{
std::cout << e.what() << '\n';
}
std::cout << '\n';
}
You can play around with this on godbolt.
Btw, you can also use function-scoped try-catch
in main (or any other function for that matter):
int main()
try
{
Employee e(-4);
}
catch (std::system_error const& e)
{
std::cout << e.what() << "\n\n";
}
The second paragraph you quoted from C++ Prime tries to explain what happens when the exception is thrown when constructing the arguments for the constructor, i.e. if you were to do something like
int maybe_throw(int x)
{
if (x < 0) throw std::runtime_error("Negative value received in `maybe_throw");
return 2*x;
}
int main()
{
Employee e(maybe_throw(-2));
}
What C++ Primer tries to explain is that in this case the Employee
constructor's catch-block will not be used because the exception is caused when creating the parameter to the constructor, not during execution of the constructor itself.
Upvotes: 2
Reputation: 473577
I didn't understand the second and third paragraphs; it looks to me like it is contradictory, because it is said that to handle exceptions from a constructor-init list we use ctor-function-try block, and from the other side it said that "it's worth noting that an exception... such exceptions are not a part from function-try-block...".
Those "..."s are important. The actual quote was:
an exception can happen while initializing the constructor's parameters
Emphasis added. That is, the paragraph is about the initialization of parameters before calling the constructor.
And now the exception in not handled and
std::terminate
is called?!
Precisely. This behavior has nothing to do with the text you quoted. This happens because function-level try
blocks on constructors cannot swallow exceptions.
If a member of an object fails to be constructed, the object itself has failed to be constructed. Members of an object are not optional (well, except for std::optional
ones ;) ); they must be properly constructed. Employee
's constructor failed to initialize Employee::age_
. The function catch
block was called.
But the Employee
object still failed to be constructed. There is only one way in C++ to communicate that an object has failed to be constructed: throwing an exception. And C++ just so happens to have an exception right there in the constructor's function try
block.
As such, when a constructor's function-level catch
block exits, the exception is always rethrown, exactly as if you had issued a throw e;
at the end of the block.
The only thing a constructor's function-level try/catch
block can do is cleanup. They cannot stop the propagation of an exception. Reasonable use of RAII makes constructor function try/catch
blocks unnecessary in nearly all cases.
Upvotes: 2
Reputation:
So here is what second paragraph is saying, if you have constructor like this:
className::className (something a) try : member(a) {
} catch () {}
would handle exception of member's constructor. So if member()
throws exception it will be handled by catch()
where as if you have this:
className::className (something a) : member(a) {
try { // this is called function try/catch on constructor
} catch () {}
}
if member throws an exception it will not be handled by catch statement of constructor.
Third paragraph is already shown in @Corristo's answer, but is basically explaining the behavior that you are seeing. You have to have try/catch in function where you are constructing your object.
Upvotes: 1