Warning: definition of implicit copy constructor is deprecated
Asked Answered
L

4

49

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?

Luing answered 15/8, 2018 at 17:28 Comment(9)
It doesn't say the copy constructor won't be generated, it's saying that implicit generation behavior is deprecated. It has been since C++11. You can add MyError(MyError const&) = default; to suppress the warning. You can also get rid of the destructor definition because it will be implicitly virtual due to the base class' destructor being virtualCitronellal
You do not have to manually create override for destructor in derived class. Virtual one in base is enough.Toadeater
Can't reproduce with regular clang or gcc. Must be a clang-cl quirk. Try not defining a destructor.Euridice
had to add switch -Wdeprecated to reproduce it. Looks like problem is a configuration of compiler. Do you use some custom compiler options?Styria
@n.m You need to compile using clang++ -Weverything to see the warning.Luing
@Linoliumz still no such warning with clang++-6.0 and -Weverything (there's a different warning about no out-of-line virtual method definitions).Euridice
@n.m I cannot reproduce the warning using clang++-6.0 either. But on Windows x64 with clang-cl /Wall (version 6.0) I get the warning.Luing
@Linoliumz Take a look on link I've provided, it is a clang.Styria
Starting from Visual Studio 2022 version 17.7 (compiler version 19.37) with /Wall command-line switch, the same warning appears warning C5267: definition of implicit copy constructor for 'MyError' is deprecated because it has a user-provided destructorChlorohydrin
M
52

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.

Mcginty answered 15/8, 2018 at 19:11 Comment(5)
Thanks for your detailed explanation. I will simply remove my destructor as it is not needed.Luing
The reason why I was cautious about removing the virtual destructor is that I remember C++ compilers warning about base classes with virtual methods but without virtual destructor. But I guess this is a different issue.Luing
Please do note, that a virtual destructor is still required if you're ever going to be deleting objects through a pointer to base. In your particular case, std::exception has a constructor declared virtual, so you do not need to concern yourself with it. If you are creating a user class, for which you own the base class, you should most certainly declare a virtual destructor inside the base. As the linked article about the rule of zero is saying, in such cases you should explicitly default all 5 special member functions.Mcginty
My previous comment should state "std::exception has a destructor declared virtual". There is no such thing as a virtual constructor, and saying constructor there was a typo.Mcginty
While reading your "constructor declared virtual" I figured out you actually meant "destructor declared virtual" ;-)Luing
T
10

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.

Toadeater answered 15/8, 2018 at 17:35 Comment(4)
OK, thanks. As I am looking for a simple design and don't need a custom destructor I will simply remove the destructor.Luing
this is not work, I made it to default ,still not work.Tertias
@NicholasJela define "not work" need details.Toadeater
And if you start adding defaulted copy member functions, you will have accidentally disabled move operations per this overview: howardhinnant.github.io/smf.jpg. Which means that you also have to default move member functions.Pearle
N
6

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

Nervy answered 17/10, 2019 at 22:52 Comment(0)
H
1

This will make your code compile (but not work) in clang 13:

   MyError(const MyError&) {};
   MyError(MyError&&) {};
   MyError& operator=(const MyError&) {};
   MyError& operator=(MyError&&) {};

You will need to fill in the appropriate code for the copy constructor,
but also be aware that you don't need all 4 functions, only the ones that are being called.

Heteromerous answered 22/2, 2022 at 18:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.