The C++ standard library provides some predefined exceptions so that a
program doesn't have to create its own integer codes or character
literals for every kind of error. They all derive from a single base
class called std::exception, and have a member function called what that returns a descriptive string.
The following is a minimal program that throws and catches the base type.
#include <iostream>
#include <exception>
int main()
{
try
{
// Signal the error by throwing an exception
throw std::exception{};
}
catch (std::exception e)
{
std::cout << e.what() << '\n';
}
return 0;
}
Throwing std::exception{} throws a temporary object of the base class, and the try block begins exception handling. The matching catch clause receives a copy of that object into e, and e.what() returns a null terminated character array that describes the exception; on most systems, the description will simply be "std::exception". The stream insertion will then print that message, and the program will continue executing normally.
More specific types of standard exceptions exist for common categories of error, and std::runtime_error is one of them, designed for problems that can only be detected during program execution.
#include <iostream>
#include <stdexcept>
int main()
{
try
{
throw std::runtime_error{"something went wrong"};
}
// Handle the matching exception here
catch (std::runtime_error e)
{
std::cout << e.what() << '\n';
}
return 0;
}
The constructor to std::runtime_error also takes an argument that will be returned by what()
when the exception is caught, so in the following program the catcher
can read whatever message was supplied when the exception was thrown.
The text produced in this case will be "something went wrong".
A related type is std::logic_error,
designed for situations such as passing bad arguments or failing to
satisfy a precondition; essentially any problem that "the programmer"
could have prevented.
#include <iostream>
#include <stdexcept>
int main()
{
try
{
throw std::logic_error{"precondition violated"};
}
catch (std::logic_error e)
{
std::cout << e.what() << '\n';
}
return 0;
}
The constructor also stores this string for what(), so like with the previous example the program would print "precondition violated". Here, the distinction between std::runtime_error and std::logic_error is that the argument to std::logic_error indicates that "the programmer" made a mistake, and the argument to std::runtime_error indicates that the program somehow failed due to something outside of its control.
Because every standard exception class derives from std::exception, a single catch
clause for the base type will catch any of them, even if some exception
handler in a library call somewhere previously caught the exception and
rethrew it.
#include <iostream>
#include <stdexcept>
int main()
{
try
{
throw std::runtime_error{"disk full"};
}
catch (std::exception e)
{
std::cout << e.what() << '\n';
}
return 0;
}
The thrown object is actually a std::runtime_error, but the catch clause declares a parameter for std::exception, so that the object is sliced down to the base class when the parameter is initialized. what()
is a virtual member function, so it will still execute the virtual
function which is associated with the derived object, and this will
still return "disk full".
However, if we don't want
to just treat all exceptions the same, we can set up exception handlers
for different specific exception types before a catch clause for the base class.
#include <iostream>
#include <stdexcept>
int main()
{
try
{
throw std::invalid_argument{"negative size"};
}
catch (std::invalid_argument e)
{
std::cout << "invalid argument: " << e.what() << '\n';
}
catch (std::exception e)
{
std::cout << "other exception: " << e.what() << '\n';
}
return 0;
}
std::invalid_argument is a kind of std::logic_error,
and in this case the first exception handler will match the thrown
exception exactly and handle it, then the second handler will not be
checked.
Similar standard types are readily available when functions need to signal a problem.
#include <iostream>
#include <stdexcept>
int divide(int a, int b)
{
if (b == 0)
throw std::runtime_error{"division by zero"};
return a / b;
}
int main()
{
try
{
std::cout << divide(10, 0) << '\n';
}
catch (std::runtime_error e)
{
std::cout << e.what() << '\n';
}
return 0;
}
When b has a value of zero, then a std::runtime_error with the message "division by zero" is thrown and caught in main(), and the program prints the message.
0 comments
No comments yet. Be the first.