I'm writing a small hierarchy of exception classes for a C++ application I'm developing, and I'm having trouble deriving indirectly from std::runtime_error
. Here is code analogous to what I've written so far:
class RuntimeException : public virtual boost::exception, public virtual std::runtime_error {
public:
virtual ~RuntimeException() {}
RuntimeException() : runtime_error("A RuntimeException occurred.") {}
RuntimeException(const std::string& what) : runtime_error(what) {}
};
class IllegalArgumentException : public virtual RuntimeException {
public:
IllegalArgumentException() : RuntimeException("An IllegalArgumentException occurred.") {}
IllegalArgumentException(const std::string& what) : RuntimeException(what) {}
};
The RuntimeException
class compiles without issue, but IllegalArgumentException
refuses to compile on VS2015, generating the error: no default constructor exists for class "std::runtime_error"
for both constructors of IllegalArgumentException
. This challenges my understanding of C++ inheritance hierarchies, as I expected this code to compile fine.
My understanding is that IllegalArgumentException
should compile because, although it is true that std::runtime_error
does not have a default constructor, its constructor is being called by the constructor for RuntimeException
. But obviously this must be false, as the compiler is rejecting it. It seems to want me to call the std::runtime_error
constructor directly from the IllegalArgumentException
constructor (the compiler error goes away when I do so), but this seems wrong because then I would be calling the constructor for std::runtime_error
twice: once in the constructor for RuntimeException
, and again in the constructor for IllegalArgumentException
.
Is this safe and/or efficient to do? If not, why does the compiler seem to encourage it? I could just derive from std::exception
and implement the std::string
myself as a member variable, but I thought it would be easier to derive from a standard class that has already implemented this. Is this the wrong approach to take? Additionally, is the fact that I'm deriving virtually from both boost:exception
and std::runtime_error
contributing to this issue?
When using virtual
inheritance the constructor call of the virtual
base is the responsibility of the most derived class rather than the responsibility of any intermediate class. The reason is obvious: the use of virtual
inheritance indicates that there is an expectation that there are actually multiple derived classes using the base class. Which one of these derived classes would be responsible for constructing the virtual
base?
So, the constructor of any of the derived classes needs to provide an argument to the virtual
base, e.g.:
IllegalArgumentException::IllegalArgumentException(std::string const& what)
: std::runtime_error(what)
, RuntimeException(what) {
}
To avoid having intermediate bases call the constructor of the virtual
base classes intended for virtual
inheritance often provide a default constructor. Of course, that opens up the possibility that the most derived class incorrectly relies on the proper constructor being called by one of its bases.