Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Warning: definition of implicit copy constructor is deprecated

I have a warning in my C++11 code that I would like to fix correctly but I don't really know how. I have created my own exception class that is derived from std::runtime_error:

class MyError : public std::runtime_error
{
public:
    MyError(const std::string& str, const std::string& message)
      : std::runtime_error(message),
        str_(str)
    { }

    virtual ~MyError()
    { }

    std::string getStr() const
    {
        return str_;
    }

  private:
      std::string str_;
};

When I compile that code with clang-cl using /Wall I get the following warning:

warning: definition of implicit copy constructor for 'MyError' is deprecated 
         because it has a user-declared destructor [-Wdeprecated]

So because I have defined a destructor in MyError no copy constructor will be generated for MyError. I don't fully understand if this will cause any issues...

Now I could get rid of that warning by simply removing the virtual destructor but I always thought that derived classes should have virtual destructors if the base class (in this case std::runtime_error) has a virtual destructor.

Hence I guess it is better not to remove the virtual destructor but to define the copy constructor. But if I need to define the copy constructor maybe I should also define the copy assignment operator and the move constructor and the move assignment operator. But this seems overkill for my simple exception class!?

Any ideas how to best fix this issue?

like image 355
Linoliumz Avatar asked Aug 15 '18 17:08

Linoliumz


People also ask

What is implicit copy constructor?

An implicitly defined copy constructor will copy the bases and members of an object in the same order that a constructor would initialize the bases and members of the object.

What is the need of making copy constructor explicitly?

Copy constructor is used to initialize the members of a newly created object by copying the members of an already existing object. Copy constructor takes a reference to an object of the same class as an argument.

How many copy constructors can a class have?

This rule is very simple: The Rule of Five: Whenever you are writing either one of Destructor, Copy Constructor, Copy Assignment Operator, Move Constructor or Move Assignment Operator you probably need to write the other four. What happens if new Bar throws ?


3 Answers

You do not need to explicitly declare the destructor in a derived class:

§ 15.4 Destructors [class.dtor] (emphasis mine)

A destructor can be declared virtual (13.3) or pure virtual (13.4); if any objects of that class or any derived class are created in the program, the destructor shall be defined. If a class has a base class with a virtual destructor, its destructor (whether user- or implicitly-declared) is virtual.

In fact, it might even be detrimental to performance in some cases, as explicitly declaring a destructor will prevent the implicit generation of a move constructor and move assignment operator.

Unless you need to do something in your destructor, the best course of action would be to just omit an explicit declaration of a destructor.

If you do need a custom destructor, and are certain that the default copy ctor, copy assignment operator, move ctor and move assignment operator would do the correct thing for you, it is best to explicitly default them like so:

MyError(const MyError&) = default;
MyError(MyError&&) = default;
MyError& operator=(const MyError&) = default;
MyError& operator=(MyError&&) = default;

Some reasoning on why you're seeing the error, because this used to be perfeclty valid code in C++98:

As of C++11, implicit generation of the copy constructor is declared as deprecated.

§ D.2 Implicit declaration of copy functions [depr.impldec]

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

The rationale behind this text is the well-known Rule of three.

All quotes below are sourced from cppreference.com: https://en.cppreference.com/w/cpp/language/rule_of_three

Rule of Three

If a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three.

The reason why this rule of thumb exists is because the default generated dtor, copy ctor and assignment operator for handling different types of resources (most notably pointers to memory, but also others, like file descriptors and network sockets to name just a couple) rarely do the correct behaviour. If the programmer thought that he needed special handling for the closing of a file handle in the class destructor, he most surely wants to define how this class should be copied or moved.

For completeness, below are the often related Rule of 5, and the somewhat disputed Rule of Zero

Rule of Five

Because the presence of a user-defined destructor, copy-constructor, or copy-assignment operator prevents implicit definition of the move constructor and the move assignment operator, any class for which move semantics are desirable, has to declare all five special member functions:

Rule of Zero

Classes that have custom destructors, copy/move constructors or copy/move assignment operators should deal exclusively with ownership (which follows from the Single Responsibility Principle). Other classes should not have custom destructors, copy/move constructors or copy/move assignment operators.

like image 70
divinas Avatar answered Oct 16 '22 08:10

divinas


Now I could get rid of that warning by simply removing the virtual destructor but I always thought that derived classes should have virtual destructors if the base class (in this case std::runtime_error) has a virtual destructor.

You thought wrong. Derived classes will always have virtual destructor if you define one in base, no matter if you create it explicitly or not. So removing destructor would be simplest solution. As you can see in documentation for std::runtime_exception it does not provide it's own destructor either and it is compiler generated because base class std::exception does have virtual dtor.

But in case you do need destructor you can explicitly add compiler generated copy ctor:

MyError( const MyError & ) = default;

or prohibit it making class not copyable:

MyError( const MyError & ) = delete;

the same for assignment operator.

like image 8
Slava Avatar answered Oct 16 '22 09:10

Slava


Note: the same happens for much different code but I'm writing it here in case someone gets the same warning.

There was a bug in GCC versions 6.4 - 9.0 where using declarations for base_type's operator= and base_type's ctor in types inherited from base_type which was a class template did not actually create copy/move ctor/operators (ending in very unexpected compiler errors that an object can not be copied/moved).

Since GCC 9.0, the bug is fixed but it creates this warning instead. The warning is wrong and should not appear (the using explicitly declares constructors/operators).

  • Example code with workarounds and GCC version comparison: https://godbolt.org/z/WgIH4c
  • GCC bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89381
  • another GCC bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92145
  • Origin of this discovery: https://github.com/boostorg/spirit/issues/465
like image 5
Xeverous Avatar answered Oct 16 '22 08:10

Xeverous