15

I find The rule of Zero as also mentioned on Peter Sommerlads Slides (p.32) very compelling.

Although, I seem to remember that there was a strict rule that one has to define the destructor virtual, if the class has virtual members and is actually derived.

struct Base {
    virtual void drawYourself();
    virtual ~Base() {}
};
struct Derived : public Base {
    virtual void drawYourself();
};

The body of the destructor may even be empty (it only needs the entry in the vtbl).

I seem to remember that when use the hierarchy

int main() {
    Base *obj = new Derived{};
    obj->drawYourself(); // virtual call to Derived::drawYourself()
    delete obj; // Derived::~Derived() _must_ be called
}

then it is important that delete obj calls the correct destructor. Is it correct, that if I left out the destructor definition totally, it would not become virtual, and therefore the wrong d'tor would be called?

struct Base {
    virtual void drawYourself();
    // no virtual destructor!
};

This leads me to my final question:

  • Is the "Rule Of Zero" also true in hierarchies with virtual methods
  • or do I need to define the virtual destructor in these cases?

Edit: As I was reminded in an answer, my 1sr version of the question had the wrong assumptions. The relevant (virtual) destructor is in Base, not Derived. But my question holds: Do I need to declare (virtual) destructors at all?

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
towi
  • 21,587
  • 28
  • 106
  • 187
  • 1
    1) Of course. 2) You need to define a virtual destructor when you think you are going to `delete` polymorphically. – Shoe Feb 07 '14 at 20:36
  • Damn. I found the "Rule of Zero" so compelling because if applied correctly it had no exceptions so far (unlike everything else in C++). Now this hope is gone... ;-) – towi Feb 07 '14 at 20:42
  • 6
    `= default`ing special members does not violate the Ro0 in any way. The important part is, that you don't implement their _functionality_ yourself and instead derive it from building blocks like `unique_ptr` and `vector`, which handle a single responsibility. And tbh, even as you have it, without `= default`, is fine for a destructor - since you don't manually do anything in it. Everything is still handled by the appropriate members' destructors. – Xeo Feb 07 '14 at 20:43
  • you got a bug in the Base class – BЈовић Feb 07 '14 at 20:45
  • @BЈовић Thanks. With 4k+ points you had the licence to correct it? Err, and you had the licence to tell me exactly what, too ;-) No really, thanks. I corrected the wrong destructor in `Base´ – towi Feb 07 '14 at 21:41
  • 6
    It's worth noting that `std::shared_ptr`, if constructed from `Derived*` (or `make_shared`) will clean-up by calling `Derived::~Derived()` and does not require `Base::~Base()` to be virtual. – Ben Voigt Feb 07 '14 at 21:44
  • @BenVoigt oh that's interesting, +1 for your comment. – user3175411 Feb 07 '14 at 21:56
  • I do have rights to modify your question, but it looks like you didn't copy and paste it. That means you haven't really tried your examples, otherwise you would see it. The compiles do complain on such thing. – BЈовић Feb 07 '14 at 23:02
  • @BenVoigt I wonder how `shared_ptr` (and `unique_ptr`, too, I presume) do that. I know about custom deleters. Is that the way it does it? Yeah, must be. The constructor of `shared_ptr` sets its custom deleter by using the type it has been instantiated with. Hrmm. Correct? – towi Feb 08 '14 at 13:25
  • 3
    @towi: That's correct for `std::shared_ptr`. In `std::unique_ptr` you don't get that behavior, because the custom deleter is part of the type (i.e. all `std::unique_ptr` will delete objects the same way). So `unique_ptr` custom deleters really are not useful for polymorphism. – Ben Voigt Feb 08 '14 at 16:15

1 Answers1

8

It's actually the base destructor that has to be declared virtual, and it's automatically virtual in derived classes:

struct Base {
    virtual void drawYourself();
    virtual ~Base() = default;
};

struct Derived : public Base {
    virtual void drawYourself();
};

But other than that, the rule of zero still holds.

If you do it the way you did it, or if you leave out the virtual destructor, you simply get undefined behavior when deleteing a derived object through a base pointer.

user3175411
  • 1,002
  • 7
  • 13
  • Silly me. Lets amend my question. – towi Feb 07 '14 at 20:36
  • 3
    You might want to change "constructor" to "destructor" – Brian Bi Feb 07 '14 at 20:37
  • 1
    Not 'other than this' - this still falls under the Ro0 with `= default`. – Xeo Feb 07 '14 at 20:45
  • 1
    @Xeo yes, maybe, but one still has to remember it. *And* write `virtual` in front of it. *And* only for classes with another virtual method. *And* can spare it if there is not deriving class (which would make no sense, actually). Anyway, one still has to explain why. – towi Feb 07 '14 at 20:48