Does "The Rule of Zero" also apply for classes with virtual methods?
Asked Answered
M

1

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?

Married answered 7/2, 2014 at 20:33 Comment(10)
1) Of course. 2) You need to define a virtual destructor when you think you are going to delete polymorphically.Runesmith
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... ;-)Married
= defaulting 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.Averroism
you got a bug in the Base classAppropriate
@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´Married
It's worth noting that std::shared_ptr<Base>, if constructed from Derived* (or make_shared<Derived>) will clean-up by calling Derived::~Derived() and does not require Base::~Base() to be virtual.Spermicide
@BenVoigt oh that's interesting, +1 for your comment.Colugo
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.Appropriate
@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?Married
@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<Base, ThisDeleterType> will delete objects the same way). So unique_ptr custom deleters really are not useful for polymorphism.Spermicide
C
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.

Colugo answered 7/2, 2014 at 20:35 Comment(4)
Silly me. Lets amend my question.Married
You might want to change "constructor" to "destructor"Caerleon
Not 'other than this' - this still falls under the Ro0 with = default.Averroism
@Averroism 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.Married

© 2022 - 2024 — McMap. All rights reserved.