Is a virtual destructor needed for your Interface, if you always store it in a shared_ptr?
Asked Answered
K

1

13

Since boost::/std::shared_ptr have the advantage of type-erasing their deleter, you can do nice things like

#include <memory>

typedef std::shared_ptr<void> gc_ptr;

int main(){
  gc_ptr p1 = new int(42);
  gc_ptr p2 = new float(3.14159);
  gc_ptr p3 = new char('o');
}

And this will correctly delete all pointer thanks to the correct deleter being saved.

If you ensure that every implementation of your interface always gets created with shared_ptr<Interface> (or make_shared<Interface>), do you actually need a virtual destructor? I would declare it virtual anyways, but I just want to know, since shared_ptr will always delete the type it was initialized with (unless another custom deleter is given).

Kikelia answered 9/7, 2011 at 12:28 Comment(7)
possible duplicate of shared_ptr magic :)Adoree
@Armen: This is not a duplicate, he is not asking how shared_ptr does it, but whether you should use a virtual destructor knowing that shared_ptr does that magic.Oleum
@David: No, he doesn't. He says he will use a virtual destructor anyway. He's asking whether it's OK not to have one. So it is a duplicateAdoree
Yes it's true. However I personally would worry about doing it. One day I'm going to decided "oh this doesnt need a shared_ptr, I'll just use a pointer to the base class", and everything breaks subtly. I'd regard it as fragile code that external code making reasonable assumptions about how classes are implemented could easily break, and not do it unless I could prove there was a requirement that could only be met by doing so.Mistrust
Well... if you have shared_ptr instances that could point to a derived type, then you're almost certainly using virtual member functions anyway, right? Otherwise, what's the point? So in that case, making the destructor virtual doesn't cost anything... except maybe a little extra typing :)Fly
Does your example actually make non-trivial use of the shared_ptr's type erasure? The deleter in all three cases is just delete(void*). I don't see the connection between a custom deleter and deletion-through-base-pointer.Vassaux
@Kerrek: No, the deleter is different for all three cases. They all maybe take a void*, but cast it to the right type, int, float and char respectively.Kikelia
B
14

I would still follow the common rule for classes that are meant to be derived:

Provide either a public virtual destructor or a protected non-virtual destructor

The reason is that you cannot control all of the uses, and that simple rule means that the compiler will flag if you try to delete through the wrong level in the hierarchy. Consider that shared_ptr does not guarantee that it will call the appropriate destructor, only that it will call the destructor of the static type that was used as argument:

base* foo();
shared_ptr<base> p( foo() );

If base has a public non-virtual destructor and foo returns a type that derives from base, then shared_ptr will fail to call the correct destructor. If the destructor of base is virtual, everything will be fine, if it is protected, the compiler will tell you that there is an error there.

Bond answered 9/7, 2011 at 12:38 Comment(5)
"I would declare it virtual anyways, [...]". :) Good point about not being able to control all instantiation points. Though, you can always make-do with a named constructor, but that probably doesn't look so nice.Kikelia
Warning: Protected destructors do not currently answer true for is_nothrow_destructible<T>::value even if they don't throw an exception. For that I reason I would favor the public option.Spada
@Howard: thanks for info about is_nothrow_destructible. It seems to do the right thing. Why would you let the fact that it currently correctly reports "not destructible" for a non-destructible thingy, make you change the thingy to destructible?Jhvh
@Xeo: Can you really control instantiation? If your class is meant to be a base, then it means that I can write my own extension, and you cannot control how I allow users to instantiate my objects.Oleum
@DavidRodríguez-dribeas In the end, the other programmers can always do as they wish. The best you can really do is a documented policy and roadblocks for attempts to circumvent.Oilskin

© 2022 - 2024 — McMap. All rights reserved.