Discussion:
[std-proposals] Make deconstructor virtual by default for pure virtual classes
Michael van der Werve
2018-10-11 15:09:45 UTC
Permalink
Currently, you need to *explicitly *specify the destructor as virtual, even
if all methods are pure virtual.

Example which does not work correctly.
class Base
{
public:
// should be overridden
virtual void doSomething() = 0;

// define destructor
virtual ~Base() = default;
};

class Derived : public Base
{
public:
// override the function
virtual void doSomething() override {}

// define the destructor
virtual ~Derived()
{
// do some fancy destructing
std::cout << "destructed" << std::endl;
}
};

And then running the following code
// create a derived instance
Derived *derived = new Derived();

// cast it to the base
Base *base = derived;

// now delete it as the base
delete base;

In the example, the derived class destructor will not be called. It can be
solved by simply altering the base class as such.
class Base
{
public:
// should be overridden
virtual void doSomething() = 0;

// add a virtual destructor
virtual ~Base() = default;
};

And naturally, everything works as expected.

So let me ask you this, is there any reason that a virtual destructor
should not simply be assumed when the class only has pure virtual
functions? There will be a virtual method table anyway, so why could it not
be assumed then?


I’d propose to make it the default, where you *can* make it non-virtual by
specifying it explicitly if you want to disallow deleting by a pointer for
example. Wouldn't that make more sense?
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/17fe64b2-7c78-492e-814d-fef72e3a668b%40isocpp.org.
Gašper Ažman
2018-10-11 15:13:12 UTC
Permalink
Well, it's a non-backwards-compatible change that makes a yet another
special case. This language has a long history of not flipping defaults
once they've been established, if the reason isn't EXTREMELY compelling.

So... even though what you're proposing makes sense to do, this change
*silently breaks ABI* for code that intended to not have virtual
destructors, for not that much gain.

Therefore my personal read is you won't get it through the committee.

Gašper
Post by Michael van der Werve
Currently, you need to *explicitly *specify the destructor as virtual,
even if all methods are pure virtual.
Example which does not work correctly.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// define destructor
virtual ~Base() = default;
};
class Derived : public Base
{
// override the function
virtual void doSomething() override {}
// define the destructor
virtual ~Derived()
{
// do some fancy destructing
std::cout << "destructed" << std::endl;
}
};
And then running the following code
// create a derived instance
Derived *derived = new Derived();
// cast it to the base
Base *base = derived;
// now delete it as the base
delete base;
In the example, the derived class destructor will not be called. It can be
solved by simply altering the base class as such.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// add a virtual destructor
virtual ~Base() = default;
};
And naturally, everything works as expected.
So let me ask you this, is there any reason that a virtual destructor
should not simply be assumed when the class only has pure virtual
functions? There will be a virtual method table anyway, so why could it not
be assumed then?
I’d propose to make it the default, where you *can* make it non-virtual
by specifying it explicitly if you want to disallow deleting by a pointer
for example. Wouldn't that make more sense?
--
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an
To view this discussion on the web visit
https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/17fe64b2-7c78-492e-814d-fef72e3a668b%40isocpp.org
<https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/17fe64b2-7c78-492e-814d-fef72e3a668b%40isocpp.org?utm_medium=email&utm_source=footer>
.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAANG%3DkUMe%2Bv9nD5NbuCWkEV5dLKbAi6haH06CDvJeDdWyt9r6Q%40mail.gmail.com.
Michael van der Werve
2018-10-11 15:20:17 UTC
Permalink
Sorry, I noticed a mistake in the example so I reposted it (couldn't find
the edit button). However, I see your point.
Post by Gašper Ažman
Well, it's a non-backwards-compatible change that makes a yet another
special case. This language has a long history of not flipping defaults
once they've been established, if the reason isn't EXTREMELY compelling.
So... even though what you're proposing makes sense to do, this change
*silently breaks ABI* for code that intended to not have virtual
destructors, for not that much gain.
Therefore my personal read is you won't get it through the committee.
Gašper
Post by Michael van der Werve
Currently, you need to *explicitly *specify the destructor as virtual,
even if all methods are pure virtual.
Example which does not work correctly.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// define destructor
virtual ~Base() = default;
};
class Derived : public Base
{
// override the function
virtual void doSomething() override {}
// define the destructor
virtual ~Derived()
{
// do some fancy destructing
std::cout << "destructed" << std::endl;
}
};
And then running the following code
// create a derived instance
Derived *derived = new Derived();
// cast it to the base
Base *base = derived;
// now delete it as the base
delete base;
In the example, the derived class destructor will not be called. It can
be solved by simply altering the base class as such.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// add a virtual destructor
virtual ~Base() = default;
};
And naturally, everything works as expected.
So let me ask you this, is there any reason that a virtual destructor
should not simply be assumed when the class only has pure virtual
functions? There will be a virtual method table anyway, so why could it not
be assumed then?
I’d propose to make it the default, where you *can* make it non-virtual
by specifying it explicitly if you want to disallow deleting by a pointer
for example. Wouldn't that make more sense?
--
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an
To view this discussion on the web visit
https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/17fe64b2-7c78-492e-814d-fef72e3a668b%40isocpp.org
<https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/17fe64b2-7c78-492e-814d-fef72e3a668b%40isocpp.org?utm_medium=email&utm_source=footer>
.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/4082ddd4-097b-4652-ace2-7153cb4c26ea%40isocpp.org.
p***@lib.hu
2018-10-18 22:45:38 UTC
Permalink
Yes, it would be more logical is the rules worked along the guidelines: if
a class has virtual functions, it should have virtual destructor.

But that ship is gone. We have 30 years of code written with the
"destructor is nonvirtual" rule in mind. I doubt you can provide a good
explanation on why is it good idea to just break everyone's code -- and how
that will be safe.

Yes, writing those virtual dtors at root of hierarchies is PITA. But
fortunately it is then propagated. So overall the situation is rare enough
to meet.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/8a394f9d-4be8-4d74-988c-e1eb0a5e252d%40isocpp.org.
Michael van der Werve
2018-10-11 15:18:34 UTC
Permalink
Currently, you need to specify the destructor as virtual, even if all
methods are pure virtual.

Example which does not work correctly.
class Base
{
public:
// should be overridden
virtual void doSomething() = 0;
};

class Derived : public Base
{
public:
// override the function
virtual void doSomething() override {}

// define the destructor
virtual ~Derived()
{
// do some fancy destructing
std::cout << "destructed" << std::endl;
}
};

And then running the following code
// create a derived instance
Derived *derived = new Derived();

// cast it to the base
Base *base = derived;

// now delete it as the base
delete base;

In the example, the derived class destructor will not be called. It can be
solved by simply altering the base class as such.
class Base
{
public:
// should be overridden
virtual void doSomething() = 0;

// add a virtual destructor
virtual ~Base() = default;
};


And naturally, everything works as expected.

So let me ask you this, is there any reason that a virtual destructor
should not simply be assumed when the class only has pure virtual
functions? There will be a virtual method table anyway, so why could it not
be assumed then?


I’d propose to make it the default, where you _can_ make it nonvirtual if
you want to disallow deleting by a pointer explicitly.


PREVIOUSLY POSTED & EDITED: there was an error in the example, and I
couldn't find an edit post button.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/144065d2-ab69-4a75-8544-7236a1850dc3%40isocpp.org.
Richard Smith
2018-10-11 20:09:37 UTC
Permalink
Post by Michael van der Werve
Currently, you need to specify the destructor as virtual, even if all
methods are pure virtual.
Example which does not work correctly.
class Base
{
// should be overridden
virtual void doSomething() = 0;
};
class Derived : public Base
{
// override the function
virtual void doSomething() override {}
// define the destructor
virtual ~Derived()
{
// do some fancy destructing
std::cout << "destructed" << std::endl;
}
};
And then running the following code
// create a derived instance
Derived *derived = new Derived();
// cast it to the base
Base *base = derived;
// now delete it as the base
delete base;
In the example, the derived class destructor will not be called. It can be
solved by simply altering the base class as such.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// add a virtual destructor
virtual ~Base() = default;
};
And naturally, everything works as expected.
So let me ask you this, is there any reason that a virtual destructor
should not simply be assumed when the class only has pure virtual functions?
Yes. Consider:

shared_ptr<Base> make() { return std::make_shared<Derived>(); }

This does not need the destructor to be virtual to function properly
(shared_ptr type-erases the destruction for the most-derived type), so
making the destructor virtual makes this code less efficient for no benefit.

Likewise:

struct FooBase {
virtual Base *get();
// ...
virtual ~FooBase();
};
struct FooDerived {
Derived *get() override { return &d; }
// ...
private:
Derived d;
};

Here, again, there is no benefit of making the Base destructor virtual,
because Base objects are never intended to be destroyed polymorphically.
(The disadvantage of a virtual destructor in this case is that it makes the
vtable larger and potentially makes Derived destruction non-trivial.)

The properties that you can make polymorphic calls on an object and that
you can destroy it polymorphically are correlated, but they are not
fundamentally tied together.

Disallowing *deletion* of an object of an abstract class type with a
non-virtual destructor would make sense as a language rule; indeed,at least
one compiler already warns on that by default.
Post by Michael van der Werve
There will be a virtual method table anyway, so why could it not be
assumed then?
I’d propose to make it the default, where you _can_ make it nonvirtual if
you want to disallow deleting by a pointer explicitly.
PREVIOUSLY POSTED & EDITED: there was an error in the example, and I
couldn't find an edit post button.
--
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an
To view this discussion on the web visit
https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/144065d2-ab69-4a75-8544-7236a1850dc3%40isocpp.org
<https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/144065d2-ab69-4a75-8544-7236a1850dc3%40isocpp.org?utm_medium=email&utm_source=footer>
.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAOfiQqnOFwfv_ebt3CeDvF934PEv5F3RLCMjibcHEyn2E1BRjw%40mail.gmail.com.
Magnus Fromreide
2018-10-11 21:23:03 UTC
Permalink
Post by Michael van der Werve
Currently, you need to *explicitly *specify the destructor as virtual, even
if all methods are pure virtual.
Example which does not work correctly.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// define destructor
virtual ~Base() = default;
};
class Derived : public Base
{
// override the function
virtual void doSomething() override {}
// define the destructor
virtual ~Derived()
{
// do some fancy destructing
std::cout << "destructed" << std::endl;
}
};
And then running the following code
// create a derived instance
Derived *derived = new Derived();
// cast it to the base
Base *base = derived;
// now delete it as the base
delete base;
In the example, the derived class destructor will not be called. It can be
solved by simply altering the base class as such.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// add a virtual destructor
virtual ~Base() = default;
};
And naturally, everything works as expected.
So let me ask you this, is there any reason that a virtual destructor
should not simply be assumed when the class only has pure virtual
functions? There will be a virtual method table anyway, so why could it not
be assumed then?
I’d propose to make it the default, where you *can* make it non-virtual by
specifying it explicitly if you want to disallow deleting by a pointer for
example. Wouldn't that make more sense?
There is also the other reasonable kind of interface:

class fooInterface
{
protected:
~fooInterface() = default;
public:
virtual void doFooThing() = 0;
};

This one is then passed to a function that needs a class with a fooInterface
but have no interest in deleting that object.

The destructor of that class might very well be virtual but a consumer of a
fooInterface doesn't care about that at all.

/MF
Post by Michael van der Werve
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/17fe64b2-7c78-492e-814d-fef72e3a668b%40isocpp.org.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/20181011212302.GA18849%40noemi.bahnhof.se.
Richard Hodges
2018-10-18 23:14:55 UTC
Permalink
Currently, you need to explicitly specify the destructor as virtual,
even if all methods are pure virtual.
This is fine when using type-erased destructors, as in the case of
shared_ptr.
example:
std::shared_ptr<Base> test(){ return std::make_shared<Derived>();}
int main(){ test()->doSomething();
// Derived::~Derived() is called here}
It's worth noting that if you replace shared_ptr with unique_ptr in the
above code, gcc will not warn you of your mistake. It probably should
be mandated in the standard that this is UB, diagnostic absolutely
required.
Example which does not work correctly.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// define destructor
virtual ~Base() = default;
};
class Derived : public Base
{
// override the function
virtual void doSomething() override {}
// define the destructor
virtual ~Derived()
{
// do some fancy destructing
std::cout << "destructed" << std::endl;
}
};
And then running the following code
// create a derived instance
Derived *derived = new Derived();
// cast it to the base
Base *base = derived;
// now delete it as the base
delete base;
In the example, the derived class destructor will not be called. It
can be solved by simply altering the base class as such.
class Base
{
// should be overridden
virtual void doSomething() = 0;
// add a virtual destructor
virtual ~Base() = default;
};
And naturally, everything works as expected.
So let me ask you this, is there any reason that a virtual destructor
should not simply be assumed when the class only has pure virtual
functions? There will be a virtual method table anyway, so why could
it not be assumed then?
I’d propose to make it the default, where you can make it non-virtual
by specifying it explicitly if you want to disallow deleting by a
pointer for example. Wouldn't that make more sense?
--
You received this message because you are subscribed to the Google
Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it,
To view this discussion on the web visit https://groups.google.com/a/
isocpp.org/d/msgid/std-proposals/17fe64b2-7c78-492e-814d-
fef72e3a668b%40isocpp.org.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/1539904495.24185.37.camel%40gmail.com.
Matthew Woehlke
2018-10-19 13:49:34 UTC
Permalink
Post by Richard Hodges
Currently, you need to explicitly specify the destructor as
virtual, even if all methods are pure virtual.
This is fine when using type-erased destructors, as in the case of
shared_ptr. example: std::shared_ptr<Base> test(){ return
std::make_shared<Derived>();} int main(){
test()->doSomething(); // Derived::~Derived() is called here} It's
worth noting that if you replace shared_ptr with unique_ptr in the
above code, gcc will not warn you of your mistake. It probably
should be mandated in the standard that this is UB, diagnostic
absolutely required.
Do you mean making the warning suggested by
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58876 a hard error
instead? (I'd probably go for that...)
--
Matthew
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f647a707-21d0-05b0-4b35-6ff4f03d44f1%40gmail.com.
Richard Hodges
2018-10-19 13:58:02 UTC
Permalink
Post by Matthew Woehlke
Post by Richard Hodges
Currently, you need to explicitly specify the destructor as
virtual, even if all methods are pure virtual.
This is fine when using type-erased destructors, as in the case of
shared_ptr. example: std::shared_ptr<Base> test(){ return
std::make_shared<Derived>();} int main(){
test()->doSomething(); // Derived::~Derived() is called here} It's
worth noting that if you replace shared_ptr with unique_ptr in the
above code, gcc will not warn you of your mistake. It probably
should be mandated in the standard that this is UB, diagnostic
absolutely required.
Do you mean making the warning suggested by
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58876 a hard error
instead? (I'd probably go for that…)
Yes. In my view if UB is at all detectable it should always be a hard error, unless the programmer puts in some signal to indicate that the UB is desired (which would be a strong signal that the language is deficient and needs a new feature).
Post by Matthew Woehlke
--
Matthew
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/46DDFD13-DDDC-4376-BF66-FF41F1A79BB1%40gmail.com.
Loading...