I am a bit confused on where I should throw an exception and where I should catch it. Right now I am throwing an invalid_argument as soon as someone creates a book with an invalid page.
In my main function I am then creating a book with an invalid page and I am catching that exception and writing out the message. Is this how you are supposed to do it? Because right now I am only catching one single instance of a book. If I tried to create another book outside the try catch block with an invalid page I woudnt be able to catch it. Is this really necessary? Is this how I'm supposed to handle exceptions?
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;
class Book
{
public:
string title {};
string author {};
int pages {};
int readers {};
Book(string const& t, string const& a, int const p)
: title{t}, author{a}, pages{p}, readers{0}
{
if (pages <= 0)
throw invalid_argument ("Invalid page!");
}
void print() const
{
cout << "Title: " << title << "\nAuthor: " << author
<< "\nPages: " << pages << "\nReaders: " << readers << endl;
}
};
int main()
{
try
{
Book book_1("Harry Potter", "JK Rowling", 0);
}
catch(const exception& e)
{
cerr << "Exception: " << e.what() << endl;
}
//book_1.print();
return 0;
}
CodePudding user response:
Yes, if you call a function that may throw an exception, at some point, you will need to handle it. The way to do it is to catch the exception.
Is it really necessary?
Well it depends. If in your case you ensure that you won't create your objects with invalid arguments, you don't need to catch an exception that'll never be raised.
Exception handling is only required when you don't know (you have no guarantee), for example when the page parameter is given from a user input, a file or anything that is unknown/not predictable/not controlled by the code base.
That being said, in your case you can do even better, just replace:
int pages;
with:
unsigned int pages;
And then it would not be possible to give an invalid number anymore. Which means you can completely remove the exception handling off your code.
The above proposal didn't take into account that 0
is considered invalid as well.
CodePudding user response:
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.)