0

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.

MAChitgarha
  • 3,728
  • 2
  • 33
  • 40
  • 4
    I have a bad feeling with this kind of hierarchy, bot no expertise. As far as I remember you create a diamond, because there are two ways from `LogicException` to `std::exception`, could it be that you have to use [`virtual`](https://stackoverflow.com/a/21607/2932052) in one of the base classes of `LogicException`? – Wolf May 02 '20 at 16:02
  • 2
    I agree. The object hierarchy needs to be rethought. – Paul Sanders May 02 '20 at 16:03
  • @Wolf About the diamond, yes, it is somehow. However, nothing changes after making things `virtual`, anywhere. Maybe the reason is I can't make `std::logic_error` virtual. – MAChitgarha May 02 '20 at 16:10
  • @PaulSanders and @Wolf, could you tell the reason why you feel bad about this hierarchy? Is catching `LogicException` with catching `std::logic_error` bad? – MAChitgarha May 02 '20 at 16:12
  • 1
    It's the diamond I don't like. See if you can get rid of it. – Paul Sanders May 02 '20 at 16:12
  • BTW: class `Exception` doesn't contribute much. The `message` variable can be replaced by `std::exception::what ()` – Paul Sanders May 02 '20 at 16:48
  • @PaulSanders That's not the case. `std::exception` has no way to set a message for the exception in the child class. `std::exception::what()` is used for being replaced in the child classes, IMO. – MAChitgarha May 02 '20 at 17:05
  • 1
    @MAChitgarha You're right, my mistake. Rather unfortunate, that. – Paul Sanders May 02 '20 at 17:09
  • @MAChitgarha You may be trying to do something that isn't necessary at all, see my answer, but I may still be completely misunderstanding your real intention. – Wolf May 03 '20 at 11:04

1 Answers1

1

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.

What to throw

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.

What to catch

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 message

As 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)

Conclusion

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).

Wolf
  • 9,679
  • 7
  • 62
  • 108
  • You're mostly trying to answer my third question. Thanks for your answer, but not helping much. – MAChitgarha May 03 '20 at 14:36
  • 1
    @MAChitgarha Now as you write it, I'd say: **Yes**, don't try to use multiple inheritance for exceptions. It's hard enough to get exception handling right with single inheritance. – Wolf May 03 '20 at 16:45
  • @MAChitgarha I just added a conclusion section with, I guess, a possible solution to your problem. – Wolf May 03 '20 at 17:07
  • Thanks for the conclusion. You're right; it's bad to use multiple inheritance for exceptions/errors. KISS. – MAChitgarha May 04 '20 at 01:15