Maestro
Maestro

Reputation: 2562

How to use correctly Constructor-Function-Try-Block?

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

Answers (3)

Corristo
Corristo

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

Nicol Bolas
Nicol Bolas

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

user5550963
user5550963

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

Related Questions