Reputation: 4298
I'm trying to design a custom exception hierarchy. What I want is, to inherit from standard exceptions and errors as much as possible, to allow catching custom exceptions in the case of catching STL ones.
For instance, in the following hierachy, if you catch an instance of std::logic_error
, then an instance of LogicException
will also be caught:
-> Exception --> LogicException
/ ^
std::exception --> std::logic_error --^
So, this is the code to achieve this (excluding header guard, includes and namespace scopes, and definitions):
class Exception: public std::exception
{
public:
Exception() = delete;
Exception(const char *) noexcept(true);
Exception(const std::string &) noexcept(true);
virtual Message what() const noexcept(true);
private:
std::string message;
};
class LogicException: public Exception, public std::logic_error
{
public:
LogicException() = delete;
using Exception::Exception;
using Exception::what;
};
However, considering the following basic main function (again, excluding non-sense parts):
int main()
{
throw LogicException("Oops!");
}
I'll get the following error (compiling with GCC 10.0.1):
test.cpp: In function ‘int main()’:
test.cpp:5:29: error: use of deleted function ‘LogicException::LogicException(const char*) [inherited from Exception]’
5 | throw LogicException("e");
| ^
In file included from test.cpp:1:
./include/exception.hpp:27:34: note: ‘LogicException::LogicException(const char*) [inherited from Exception]’ is implicitly deleted because the default definition would be ill-formed:
27 | using Exception::Exception;
| ^~~~~~~~~
./include/exception.hpp:27:34: error: no matching function for call to ‘std::logic_error::logic_error()’
In file included from ./include/exception.hpp:4,
from test.cpp:1:
/usr/include/c++/10/stdexcept:131:5: note: candidate: ‘std::logic_error::logic_error(const std::logic_error&)’
131 | logic_error(const logic_error&) _GLIBCXX_NOTHROW;
| ^~~~~~~~~~~
/usr/include/c++/10/stdexcept:131:5: note: candidate expects 1 argument, 0 provided
/usr/include/c++/10/stdexcept:126:5: note: candidate: ‘std::logic_error::logic_error(std::logic_error&&)’
126 | logic_error(logic_error&&) noexcept;
| ^~~~~~~~~~~
/usr/include/c++/10/stdexcept:126:5: note: candidate expects 1 argument, 0 provided
/usr/include/c++/10/stdexcept:124:5: note: candidate: ‘std::logic_error::logic_error(const char*)’
124 | logic_error(const char*) _GLIBCXX_TXN_SAFE;
| ^~~~~~~~~~~
/usr/include/c++/10/stdexcept:124:5: note: candidate expects 1 argument, 0 provided
/usr/include/c++/10/stdexcept:120:5: note: candidate: ‘std::logic_error::logic_error(const string&)’
120 | logic_error(const string& __arg) _GLIBCXX_TXN_SAFE;
| ^~~~~~~~~~~
/usr/include/c++/10/stdexcept:120:5: note: candidate expects 1 argument, 0 provided
So, the questions are:
After a while, I think this is why the error occurs: While using Exception
constructor, the compiler tries to implicitly call the (default) constructor of std::logic_error
, which does not exist. Is that true?
Is there any way to prevent this error (e.g. somehow calling std::logic_error
's constructor explicitly) without having to explicitly declare LogicException
constructor or changing class hierarchy?
Note: The reason I don't want to explicitly declare LogicException::LogicException
is that I'd have some exception classes defined this way, thus, I don't want to add extra files defining LogicException
class or whatever.
Generally speaking, is using the above idea in designing exceptions good?
Thanks.
Upvotes: 0
Views: 51
Reputation: 10238
I'm not sure if you really want to build another exception hierarchy along with that of std::exception
(maybe I just don't get your point). As to build an exception hierarchy onto std::exception
(and its children), you don't need another base.
Since you should anyway never throw unspecific exceptions, because you will not be able to handle it in a specific way at the caller side, only add specific leaves to the existing exception hierarchy your platform supports. If you are programming standard C++, this is the std::exception
hierarchy.
You should be very careful with catching std::exception
. I'd suggest to do this only in main
(or an equivalent top-level function) as to log an error before exiting. Exceptions that you are allowed to catch are those whose conditions you understand and which you have complete control over; for instance that a file doesn't exist that the program has alternatives for or doesn't need it at all (because ist's just optional).
what
to messageAs I understand it, the what()
property is an original message from the place where the "accident" happened, it gives additional information that your program shouldn't bind to.[1] It's for "forensics" mostly just for generating output (for displaying on user interface or adding to log file). See the following small demo of my understanding:
#include <iostream>
#include <stdexcept>
class my_ex: public std::logic_error {
public:
my_ex(const char* const what): std::logic_error(what) {}
};
void just_throw_the_message(const char* const msg) {
throw my_ex(msg);
}
int main() {
try {
just_throw_the_message("I feel somewhat exceptional.");
} catch (const std::exception& e) {
std::cout << "exception caught: " << e.what() << std::endl;
}
return 0;
}
(see above code running at ideone.com)
Your initial statement was (emphasis mine):
What I want is, to inherit from standard exceptions and errors as much as possible, to allow catching custom exceptions in the case of catching STL ones.
The only reasonable answer I see to this requirement is to derive your class directly from std::exception
or one of its descendants! Don't use multiple inheritance for exceptions! Especially, as much as possible, is not much more than the error classification already defined by the hierarchy you build onto. So when in standard C++, I highly recommend inheriting (ultimately) from std::exception
.
If you already have an application-specific exception hierarchy that fits you needs, just make its base class a descendant of std::exception
.
[1] As to distinguish exceptional events programmatically, use the exception type. It also makes sense to add properties to your own program-specific exception class(es).
Upvotes: 1