Search code examples
c++exceptionconstructorabstract-classmultiple-inheritance

User-defined empty constructors in custom exception with multiple inheritance and abstract base class


I designed several custom exceptions which all inherit from the same abstract base class custom::exception and from the corresponding standard exception thanks to multiple inheritance. Thus there is a specific hierarchy in my custom exceptions and I can catch them generically or specifically.

Now I switched on all warnings without any problem with GCC but clang complains about the virtual destructor :

#include <iostream>
#include <stdexcept>

namespace custom {
    struct exception;
    struct buffer_overflow;
    struct null_pointer;
    struct out_of_range;
    //...
}

struct custom::exception {
    virtual ~exception() noexcept {}
    virtual const char* what() const noexcept = 0;
};

struct custom::null_pointer : public custom::exception, public std::logic_error {
    null_pointer(const std::string &str): std::logic_error(str) {}
    const char* what() const noexcept override { return std::logic_error::what(); }
};

int main(){
    try{
        throw custom::null_pointer("[ERROR] NULL pointer exception !");
    }
    catch(const custom::exception &error){
        std::cout << error.what() << std::endl;
    }
}

Output with g++ (MinGW.org GCC-8.2.0-3) :

[ERROR] NULL pointer exception !

Output with clang++ version 6.0.0-1ubuntu2 :

test.cpp:13:13: warning: definition of implicit copy constructor for 'exception' is deprecated because it has a
      user-declared destructor [-Wdeprecated]
    virtual ~exception() noexcept {}
            ^
test.cpp:17:16: note: in implicit copy constructor for 'custom::exception' first required here
struct custom::null_pointer : public custom::exception, public std::logic_error {
               ^
test.cpp:24:15: note: in implicit move constructor for 'custom::null_pointer' first required here
        throw custom::null_pointer("[ERROR] NULL pointer exception !");
              ^
1 warning generated.
[ERROR] NULL pointer exception !

I do not really understand because custom::exception is abstract and does not contain any member variable so it does not need user-defined constructors. But if I define those, clang does not complain :

struct custom::exception {
    exception() noexcept {}
    exception(const exception&) noexcept {}
    virtual ~exception() noexcept {}
    virtual const char* what() const noexcept = 0;
};

My questions are :

  1. Which compiler is correct ?
  2. Why do I have to define empty default & copy constructors such as above ?

Thank you.

Related questions :


Solution

  • You came across the same issue Warning: definition of implicit copy constructor is deprecated had encountered:

    In the C++ standard D.2 Implicit declaration of copy functions [depr.impldec] the draft for C++17 N4659 states:

    The implicit definition of a copy constructor as defaulted is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor. The implicit definition of a copy assignment operator as defaulted is deprecated if the class has a user-declared copy constructor or a user-declared destructor (15.4, 15.8). In a future revision of this International Standard, these implicit definitions could become deleted (11.4).

    This gives the answer to

    1.: Both compilers are correct, clang actually warns you about the deprecation, gcc does not which is also okay.

    This is ok because D(0) make a warning about it optional, also even the [[deprecated]] attribute, which also is not mandatory to issue a warning:

    An implementation may declare library names and entities described in this section with the deprecated attribute

    2. To force you to think about the so called Rule of Three: The rule of three states that since you need to define a constructor you probably you need a complex copy and or move constructor. By forcing you to explicit defining them, even with default, it acts as a insurance, the class author has thought about slicing and other problems. However since the implicit or defaulted copy constructor just copies your bases and elements, you should be fine.

    You can default, which basically does the same as the implict defined things, but you want a virtual destructor, which is otherwise the same:

    exception() noexcept = default;
    exception(const exception&) noexcept = default;
    virtual ~exception() noexcept = default;