You should catch and handle the exception where it makes sense for the program.
First of all, your program always throws the exception independent of input, because the 0 is hard-coded. This doesn't seem to be a use case for an exception. This seems to be a logic error and so it should be considered a bug that needs fixing. Why should it make sense to try to create a guaranteed invalid book?
But let's suppose the number of pages is given by user input. Then the next issue would be that you can't print a book that failed to be created. So book_1.print(); must be part of the try block.
Let's suppose you have a second book book_2, also created from input from the user. You need to ask yourself then whether it makes sense for the program logic to continue creating and using the second book if creating the first one failed.
If failing to create the first book means that there is no point in continuing with the second book, then just put everything in one try block, e.g.:
try
{
Book book_1(/*...*/);
book_1.print();
Book book_2(/*...*/);
book_2.print();
}
catch(const exception& e)
{
cerr << "Exception: " << e.what() << endl;
}
When either of the book creations fail, the program will continue executing in the catch handler and then after it, skipping over the rest of the book creations and printing. But if book_1 didn't throw on construction, then it will be printed before book_2 is "checked".
Maybe you would want
try
{
Book book_1(/*...*/);
Book book_2(/*...*/);
book_1.print();
book_2.print();
}
catch(const exception& e)
{
cerr << "Exception: " << e.what() << endl;
}
With this the program will not print any book if creation of either book fails.
If it does make sense for the program to continue execution if creation of the first book failed, then use two try/catch statements to handle the errors individually:
try
{
Book book_1(/*...*/);
book_1.print();
}
catch(const exception& e)
{
cerr << "Exception: " << e.what() << endl;
}
try
{
Book book_2(/*...*/);
book_2.print();
}
catch(const exception& e)
{
cerr << "Exception: " << e.what() << endl;
}
You might want to consider using a function or arrays and loops to make this less redundant, for example if the goal is to just repeatedly take user input, create a book and then print it, a simple loop will be helpful:
while(true)
{
try
{
/* take user input here */
if(/* some exit condition */)
{
break;
}
Book book(/* arguments from user input */);
book.print();
}
catch(const exception& e)
{
cerr << "Exception: " << e.what() << endl;
}
}
This loop will run until the exit condition is satisfied. If any standard exception (from book creation or the input operation) is thrown, the exception message will be printed and the loop continues anew.
(As an additional note: In all of the examples above all exceptions inherited from std::exception thrown in the try blocks will be caught. This means not only the invalid_argument exception from the book creation, but e.g. any exception thrown by .print() or the input handling. You may not want to handle these the same way as the invalid book creation. For example an exception from input operations could mean that there is no way for the program to continue the loop. Therefore it makes sense to use a custom class (maybe inherited from std::invalid_argument) for the throw in Book's constructor and catch only that. This way you can handle the invalid book creation specifically.)