4

In C++11, a polymorphic class (one with virtual member methods) should/must have a virtual destructor (so that delete on a base-class pointer does the expected). However, declaring an destructor explicitly deprecates the implicit generation of the copy constructor (though this may not be widely implemented by compilers) and hence also of the default constructor. Thus, for any polymorphic class to not be deprecated it must have these members

virtual ~polymorphic_class() = default;
polymorphic_class() = default;
polymorphic_class(polymorphic_class const&) = default;

explicitly defined, even if they are trivial. Am I correct? (Isn't this annoying?) What is the logic behind this? Is there any way to avoid that?

Walter
  • 44,150
  • 20
  • 113
  • 196
  • What about `virtual ~my_class() = default;`? – Xeo Nov 15 '13 at 09:30
  • @Xeo nope, doesn't make any difference; it's still *user-declared* – Walter Nov 15 '13 at 09:32
  • 1
    A polymorphic, public base should be abstract, so you need to declare a constructor explicitly anyway to make it `protected`. You can still use a `= default` default constructor, you just have mention it explicitly. – Kerrek SB Nov 15 '13 at 09:39
  • @KerrekSB *A polymorphic, public base should be abstract.* Why? – Walter Nov 15 '13 at 09:44
  • 1
    @Walter: because http://stackoverflow.com/questions/16724946/why-derive-from-a-concrete-class-is-a-poor-design – Steve Jessop Nov 15 '13 at 09:45
  • 1
    @KerrekSB: If the class is abstract, why do you need to make the constructor `protected`? It can't be instantiated anyway, except as a base class sub-object, so what problem is caused by a public constructor? Or do you just mean that making all constructors `protected` is the means by which you make it "abstract" if it happens not to literally be an abstract class, because for some reason it has no pure virtual functions? – Steve Jessop Nov 15 '13 at 09:51
  • @SteveJessop: Both should be the case as a matter of good style. Pure-virtual functions *and* protected constructors. Documenting intent, etc. – Kerrek SB Nov 15 '13 at 09:59
  • 1
    Because if there's one thing better than documenting intent, it's documenting intent twice ;-p Anyway, if it's purely a style thing then I don't need to understand it unless or until I'm working under a style guide that dictates it. – Steve Jessop Nov 15 '13 at 10:05

2 Answers2

2

Am I correct?

Yes, as per ForEveR's post.

Is there any way to avoid that?

Yes. Do this just once by implementing a base for all polymorphic classes (similarly to class Object in Java and D which is the root of all class hierarchies):

struct polymorphic {

    polymorphic()                               = default;
    virtual ~polymorphic()                      = default;

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

    // Those are not required but they don't harm and are nice for documentation
    polymorphic(polymorphic&&)                  = default;
    polymorphic& operator =(polymorphic&&)      = default;
};

Then, any class publicly derived from polymorphic will have a implicitly declared and defined (as defaulted) virtual destructor unless you declare one yourself.

class my_polymorphic_class : public polymorphic {
};

static_assert(std::is_default_constructible<my_polymorphic_class>::value, "");
static_assert(std::is_copy_constructible   <my_polymorphic_class>::value, "");
static_assert(std::is_copy_assignable      <my_polymorphic_class>::value, "");
static_assert(std::is_move_constructible   <my_polymorphic_class>::value, "");
static_assert(std::is_move_assignable      <my_polymorphic_class>::value, "");

What is the logic behind this?

I can't be sure. What follows are just speculations.

In C++98/03, the Rule of Three says that if a class needs either the copy constructor, the copy assignment operator or the destructor to be user defined, then it probably needs the three to be user defined.

Obeying the Rule of Three is a good practice but this is nothing more than a guideline. The Standard doens't force it. Why not? My guess is that people realized this rule only after the publication of the Standard.

C++11 introduced move constructor and move assignment operator, turning the Rule of Three into the Rule of Five. With benefit of hindsight, the committee wanted to enforce the Rule of Five. The idea was: if either of the five special functions is user declared then the others, but the destructor, won't be implicitly defaulted.

However, the committee didn't want to break virtually every C++98/03 code by enforcing this rule and then, decided to do it only partially:

  1. If either the move constructor or the move assignment operator is user declared then other special functions, but the destructor, will be deleted.

  2. If either of the five special functions is user declared then the move constructor and move assignment operators won't be implicitly declared.

In the case of C++98/03 well formed code, neither a move constructor nor a move assignment operator is ever user declared then, rule 1 doesn't apply. Hence when compiled with a C++11 compliant compiler C++98/03 well formed code doesn't fail to compile as a consequence of this rule. (If it does, it's for some other reasons.)

In addition, under rule 2 the move constructor and move assignment operator are not implicitly declared. This doesn't break C++98/03 well formed code either because they never expected the declaration of move operations anyway.

The deprecation mentioned in the OP and quoted in ForEveR's post suggests a possible enforcement of the Rule of Five by a future Standard. Be prepared!

Murphy
  • 3,827
  • 4
  • 21
  • 35
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • +1 interesting idea of the `polymorphic` base class. However, you cannot derive from it a polymorphic class with `noexcept` destructor (at least icpc 14.0.1 will report an error, I think). – Walter Nov 16 '13 at 18:46
  • @CassioNeri defauled move semantics shoud be marked `noexcept` – Moia Oct 11 '19 at 09:25
  • @Moia There's no need here. Indeed, `static_assert(std::is_nothrow_move_constructible::value, "");` doesn't fire (see [here](https://godbolt.org/z/f7190V)). Also see `struct B` and its comment in [C++17's](https://timsong-cpp.github.io/cppwp/n4659/except.spec#11). The text that precedes the example states conditions under which `= default` implies `noexcept`. Having said that, it might be useful to add `noexcept` for the sake of documentation and clarity. – Cassio Neri Oct 11 '19 at 16:22
1

You are correct, that should be true by standard in future, but now it's only deprecated, so every compiler should support implicitly-declared copy constructor, when destructor is virtual now.

n3376 12.8/7

If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.

And it seems to me, that you cannot make any workaround for this.

ForEveR
  • 55,233
  • 2
  • 119
  • 133
  • 2
    Ah, the dreaded "user-declared". Too bad they didn't make it "user-defined". Really. – Xeo Nov 15 '13 at 09:36