Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C.128: Should destructors be marked "override"? #721

Closed
jaredgrubb opened this issue Sep 5, 2016 · 16 comments
Closed

C.128: Should destructors be marked "override"? #721

jaredgrubb opened this issue Sep 5, 2016 · 16 comments

Comments

@jaredgrubb
Copy link

I was using the 'clang-tidy' tool on a codebase and one of its checkers (modernize-use-override) tries to verify that overridden virtual functions are marked 'override'. I was surprised to find that it also flagged destructors.

Putting 'override' on a destructor seems weird to me, but I was curious if I'm the odd one (and I should get used to it) or whether I should submit a patch to clang-tidy to ignore destructors.

Guidelines C.128 does not explicitly mention destructors; I noticed a comment by Titus in Issue #423 that mentions that others find it weird although it is consistent.

In any case, I think C.128 could have an explicit note about how to handle destructors (whichever way is considered preferred).

@MikeGitb
Copy link

MikeGitb commented Sep 6, 2016

I'd say the advantage of override is that it ensures that the base class's destructor is virtual.

Think about a base class destructor that releases some memory. Now after refactoring you are managing memory via a smart pointer instead. As a result you think you can completely remove your destructor implementation but forget that you still have to declare it as virtual.

@gdr-at-ms
Copy link
Contributor

The semantics for virtual destructor is different from "normal" virtual functions. You never override a destructor -- virtual destructors are "chained".

@MikeGitb
Copy link

MikeGitb commented Sep 12, 2016

@gdr-at-ms: Maybe I misunderstand something, but calling a virtual destructor still results in virtual function call that finds the destructor belonging to the dynamic type of the object. How is that different from calling a "regular" virtual function? The fact that destructor calls are chained is imho an orthogonal aspect, as this is true, whether it is virtual or not.

This is the essence of what I was describing:

Pre refactor:

struct Base {
    virtual ~Base() {
        //do some manual cleanup of some resources
    }
};

struct Derived : Base{
    ~Derived() override {
        //more cleanup of new resources
    }
};

int main() {
    Base* b_ptr = new Derived();
    //some code
    delete b_ptr;
}

Post refactor:

struct Base {       
    //Resources are now managed by smart pointer        
};

struct Derived : Base {
    ~Derived() override {
        //more cleanup of new resources
    }
};

int main() {
    Base* b_ptr = new Derived();
    //some code
    delete b_ptr; //<- would now be UB, but thanks to override, this is caught by the compiler
}

That being said, I've no Idea if the above is a common enough scenario to warrant the enforced usage of override on all destructors. I believe the main benefit of override for "normal" functions is about avoiding spelling mistakes, which is not relevant for destructors.

@rianquinn
Copy link
Contributor

Lol... we are having a similar discussion on our GitHub page as well link

IMO, this ticket was closed to quickly and deserves additional discussion. For example, @jaredgrubb suggested that the documentation should at the very least be updated to state it one way or another. If in fact it's decided that override should not be used, it could result in two checks within Clang Tidy being mutually exclusive. At the moment, Clang Tidy at the very least forces users to disobey the above guidance, and I think it's in everyone's best interest to achieve some sort of consensus on the matter.

For reference, this is not a bug in Clang Tidy. As can be seen here

@gdr-at-ms
Copy link
Contributor

@MikeGitb override isn't about a call. It is about the meaning of definition.

@MikeGitb
Copy link

@gdr-at-ms: I was just wondering, where - as you said - the semantics for virtual destructors differ from those of "normal" virtual functions. Maybe you can help me understand that.

But again, I don't want to particularly promote using override on virtual destructors (so far I haven't used it there myself). I was just wondering, if there would be any benefit at all.

Out of curiosity: Do you mark the destructors of derived classes explicitly virtual? If so, then I honestly don't see a disadvantage in marking them with override instead. If not, I can understand why you don't want to add noise.

In any case, I agree with Jaredgrubb, that c.128 could mention destructors explicitly.

@gdr-at-ms
Copy link
Contributor

@MikeGitb I explained that in the original message and the follow up: override is about signaling a replacement of implementation and having the compiler check that indeed we are replacing, not overloading. You never replace destructors.

In my own day-to-day coding, I don't mark destructors in derived classes as 'virtual', when they are already declared virtual in the base class.

rianquinn added a commit to rianquinn/CppCoreGuidelines that referenced this issue Sep 13, 2016
Clang Tidy has a a check called (modernize-use-override) that explicitly verifies that `override` be placed on destructors of derived classes whose base class is `virtual` as seen [here](https://github.com/Microsoft/clang-tools-extra/blob/master/test/clang-tidy/modernize-use-override.cpp#L48). This issue was brought up by @jaredgrubb in the following [ticket](isocpp#721 (comment)) and was also seen [here](Bareflank/hypervisor#208) as well. @gdr-at-ms closed the ticket stating that the C++ Core Guideline Editors have decided that `override` should not be placed on destructors, but the documentation makes no mention of this decision. The following PR addresses this issue. With the documentation updated, an issue ticket can be generated for Clang Tidy to have the destructor check modified to reflect the C++ Core Guidance.
@alexfh
Copy link

alexfh commented Sep 21, 2016

@gdr-at-ms "You never override a destructor -- virtual destructors are "chained"." is wrong.

According to [class.virtual]p6:
"Even though destructors are not inherited, a destructor in a derived class overrides a base class destructor declared virtual; see 12.4 and 12.5."

@alexfh
Copy link

alexfh commented Sep 21, 2016

(and, as mentioned before, the "chaining" aspect is there, but is completely orthogonal to overriding)

@gdr-at-ms
Copy link
Contributor

gdr-at-ms commented Sep 21, 2016

No, the chaining aspect is not orthogonal at all. It explains why the tool's insistence of your using override on destructor is misguided.

Again, I strongly encourage people to take a step back and not get too hung on details of standards wording. Think about it a second: if a virtual function isn't inherited, you shouldn't be in the business of having to override it in the first place! Sometimes, WG21 extends a usage of a term in the standards document not for the working programmers' concerns, but only to make wording much simpler -- e.g. for "implementation convenience".

I would suggest people take a step back and reflect on why we have override in the first place and what it is for -- don't rush to quote the standard texts yet:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1827.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2108.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2108.html

Every single time a tool makes the suggestion of adding override to a destructor, virtual does a better job and adds a better value. Talk to your tool vendor.

@ParadineG
Copy link

At first I would have also disagreed with @gdr-at-ms but then I remembered C.127
As rule C.128 is for functions is rule C.127 for destructors.
Adding override to destructors will be like double checking, and therefore it does less good, but it also does harm through meaning by confusing even more how virtual destructors work.
override was added mainly to help avoid bugs from spelling mistakes when writing virtual functions. I am yet to see misspelled destructor pass compilation.

In short as long something will check for rule C.127 there is no need for override in destructor.

@rianquinn
Copy link
Contributor

Clang Tidy disagrees wth C.127 as well in a way. Basically, Clang Tidy forces the use of virtual once, and all other functions are then marked override, not both as that would be redundant. Since they also enforce this for destructors, the same rules apply. The base class is marked virtual, and all other derivations are marked override.

I understand the original intent of override, and personally don't care either way (I care more about consensus), but my question would be.... does adding override to a destructor prevent a class of bugs? For example, if you forgot to label a base class destructor as virtual, would marking the derived destructors override detect that? If so, I would side with Clang Tidy. Regardless of the original intent, if a class of bugs can be prevented, it should be done, assuming of course this is in fact the case.

@gdr-at-ms
Copy link
Contributor

Not programming at all prevents a class of bugs -- the bugs you never write in the first place (including the ones you think you would avoid by writing override on a destructor.)

A better and more useful question to ask is "what should the working programmer write?" That is what these Core Guidelines are about.

The observation is that virtual is more useful on a destructor than override is -- regardless of the intensity of the tool wanting you to absolutely write override.

@vadz
Copy link
Contributor

vadz commented Nov 28, 2016

@gdr-at-ms Could you please explain how do you arrive at the conclusion that

virtual is more useful on a destructor than override is

? I see two cases:

  1. If the base class dtor is virtual, both virtual and override on the derived class dtor are used only for the documentary purposes and from this point of view they're equivalent. However the latter is more consistent with the normal methods and hence, IMHO, slightly preferable.
  2. If the base class dtor is not virtual, the use of virtual is most likely a programmer error and hides a bug such as one illustrated in a previous comment, while the use of override is a compile-time error and so is strongly preferable.

So how/why/when is virtual more useful precisely?


The only argument against using override for dtors I personally see is that it's unusual (at least IME). I don't think it's a good enough argument to advise against its use, i.e. I think this part of C.128:

If a base class destructor is declared virtual, derived class destructors should neither be declared virtual nor override

is misplaced because it directly contradicts the rationale given in its own "Reason" section just above.

knutzk added a commit to knutzk/KLFitter that referenced this issue May 11, 2018
As suggested in the c++ guidelines, keywords such as 'virtual' or 'override'
should be avoided for destructors of derived classes. Using 'virtual' for the
base class destructors remains essential.

See isocpp/CppCoreGuidelines#721
N-Dekker added a commit to SuperElastix/elastix that referenced this issue Apr 5, 2019
Added override specifiers by running Tidy-Fix (Clang Power Tools 4.10.1 in Visual Studio 2017).

Aims to fix elastix issue #110 ("-Winconsistent-missing-override on MacOS") by Harmen Stoppels (@haampie)

As I was using the default modernize-use-override options, Tidy-Fix also added `override` specifiers to destructors. (Which appears under discussion: 'C.128: Should destructors be marked "override"?' isocpp/CppCoreGuidelines#721).

Moreover, Tidy-Fix has removed `virtual` keywords that are not required.

See https://clang.llvm.org/extra/clang-tidy/checks/modernize-use-override.html
N-Dekker added a commit to SuperElastix/elastix that referenced this issue Apr 7, 2019
Added override specifiers by running Tidy-Fix (Clang Power Tools 4.10.1 in Visual Studio 2017).

Aims to fix elastix issue #110 ("-Winconsistent-missing-override on MacOS") by Harmen Stoppels (@haampie)

As I was using the default modernize-use-override options, Tidy-Fix also added `override` specifiers to destructors. (Which appears under discussion: 'C.128: Should destructors be marked "override"?' isocpp/CppCoreGuidelines#721).

Moreover, Tidy-Fix has removed `virtual` keywords that are not required.

See https://clang.llvm.org/extra/clang-tidy/checks/modernize-use-override.html
N-Dekker added a commit to SuperElastix/elastix that referenced this issue Apr 8, 2019
Added override specifiers by running Tidy-Fix (Clang Power Tools 4.10.1 in Visual Studio 2017).

Aims to fix elastix issue #110 ("-Winconsistent-missing-override on MacOS") by Harmen Stoppels (@haampie)

As I was using the default modernize-use-override options, Tidy-Fix also added `override` specifiers to destructors. Doing so appears under discussion: 'C.128: Should destructors be marked "override"?' isocpp/CppCoreGuidelines#721  Moreover, Tidy-Fix has removed `virtual` keywords that are not required. Which is OK.

Tidy-Fix made two "mistakes":
 * It accidentally added `override` keywords to the getter and setter of `ComputeZYX`, in https://github.com/SuperElastix/elastix/blob/develop/Common/Transforms/itkEulerTransform.h
 * It removed `register` keywords (which is an unrelated issue), for example in https://github.com/SuperElastix/elastix/blob/develop/Components/Metrics/KNNGraphAlphaMutualInformation/KNN/ann_1.1/src/ANN.cpp
These Tidy-Fix mistakes were manually corrected.

See https://clang.llvm.org/extra/clang-tidy/checks/modernize-use-override.html
haampie pushed a commit to haampie/elastix that referenced this issue Apr 29, 2019
Added override specifiers by running Tidy-Fix (Clang Power Tools 4.10.1 in Visual Studio 2017).

Aims to fix elastix issue SuperElastix#110 ("-Winconsistent-missing-override on MacOS") by Harmen Stoppels (@haampie)

As I was using the default modernize-use-override options, Tidy-Fix also added `override` specifiers to destructors. Doing so appears under discussion: 'C.128: Should destructors be marked "override"?' isocpp/CppCoreGuidelines#721  Moreover, Tidy-Fix has removed `virtual` keywords that are not required. Which is OK.

Tidy-Fix made two "mistakes":
 * It accidentally added `override` keywords to the getter and setter of `ComputeZYX`, in https://github.com/SuperElastix/elastix/blob/develop/Common/Transforms/itkEulerTransform.h
 * It removed `register` keywords (which is an unrelated issue), for example in https://github.com/SuperElastix/elastix/blob/develop/Components/Metrics/KNNGraphAlphaMutualInformation/KNN/ann_1.1/src/ANN.cpp
These Tidy-Fix mistakes were manually corrected.

See https://clang.llvm.org/extra/clang-tidy/checks/modernize-use-override.html
hsutter added a commit that referenced this issue Jun 16, 2019
This PR affirms that all virtual functions, *including destructors*,
should be declared exactly one of `virtual`, `override`, or `final`, and
takesa pass through the document to make the examples and guidance
consistent with that.

Of course a virtual destructor is a virtual function: It behaves
polymorphically, and it has a vtable entry that can be overwritten ==
overridden in a derived class exactly the same as any other derived
virtual override. See also [class.virtual]/7: "Even though destructors
are not inherited, a destructor in a derived class overrides a base
class destructor declared virtual; see [class.dtor] and [class.free]."

However, the following exception text currently appears in C.128:

> If a base class destructor is declared `virtual`, one should avoid
declaring derived class destructors  `virtual` or `override`. Some code
base and tools might insist on `override` for destructors, but that is
not the recommendation of these guidelines.

... but this exception is (a) not well-founded, and (b) inconsistent
with the Guidelines' practice in other examples and with the rationale a
few lines earlier for C.128 itself.

Re (a):

- The exception is overly broad: The rationale given for this exception
is entirely against marking destructors `override` (not `virtual`). So
clearly the exception to write neither keyword is too broad: At most,
the exception should be to write `virtual` rather than `override`.
- Explicit `virtual` is primarily for class users, not class authors:
The arguments given in #721 favoring this exception are from the
viewpoint of the implementation of the function (even then, the
arguments are debatable and debated). But `virtual`, `override`, and
`final` are primarily for the far larger audience of *class users and
call sites* of the function, for whom of course we should document each
declared function that is polymorphic, *especially* the destructor --
this tells calling code whether the function is safe to call through a
(smart or built-in) pointer or reference to base, which will nearly
always be the case for such types. We should not make the reader of the
code go way to look in the base classes to figure out whether a function
declared in this class is virtual or not -- the reason this Item exists
is primarily to avoid that implicit virtual antipattern via convention
and automated enforcement. For class users, all virtual functions
including destructors are equally polymorphic.

Re (b): The Guidelines already don't follow this. For instance, two
Items later (in C.130) we have this example that correctly uses
`override`:

~~~
virtual ~D() override;
~~~

... though per C.128 it should not also specify `virtual` (also fixed in
this PR).

Finally, the exception also contradicts the rationale given earlier in
the same Item.
@hsutter hsutter mentioned this issue Jun 16, 2019
hsutter added a commit that referenced this issue Aug 1, 2019
This PR affirms that all virtual functions, *including destructors*,
should be declared exactly one of `virtual`, `override`, or `final`, and
takesa pass through the document to make the examples and guidance
consistent with that.

Of course a virtual destructor is a virtual function: It behaves
polymorphically, and it has a vtable entry that can be overwritten ==
overridden in a derived class exactly the same as any other derived
virtual override. See also [class.virtual]/7: "Even though destructors
are not inherited, a destructor in a derived class overrides a base
class destructor declared virtual; see [class.dtor] and [class.free]."

However, the following exception text currently appears in C.128:

> If a base class destructor is declared `virtual`, one should avoid
declaring derived class destructors  `virtual` or `override`. Some code
base and tools might insist on `override` for destructors, but that is
not the recommendation of these guidelines.

... but this exception is (a) not well-founded, and (b) inconsistent
with the Guidelines' practice in other examples and with the rationale a
few lines earlier for C.128 itself.

Re (a):

- The exception is overly broad: The rationale given for this exception
is entirely against marking destructors `override` (not `virtual`). So
clearly the exception to write neither keyword is too broad: At most,
the exception should be to write `virtual` rather than `override`.
- Explicit `virtual` is primarily for class users, not class authors:
The arguments given in #721 favoring this exception are from the
viewpoint of the implementation of the function (even then, the
arguments are debatable and debated). But `virtual`, `override`, and
`final` are primarily for the far larger audience of *class users and
call sites* of the function, for whom of course we should document each
declared function that is polymorphic, *especially* the destructor --
this tells calling code whether the function is safe to call through a
(smart or built-in) pointer or reference to base, which will nearly
always be the case for such types. We should not make the reader of the
code go way to look in the base classes to figure out whether a function
declared in this class is virtual or not -- the reason this Item exists
is primarily to avoid that implicit virtual antipattern via convention
and automated enforcement. For class users, all virtual functions
including destructors are equally polymorphic.

Re (b): The Guidelines already don't follow this. For instance, two
Items later (in C.130) we have this example that correctly uses
`override`:

~~~
virtual ~D() override;
~~~

... though per C.128 it should not also specify `virtual` (also fixed in
this PR).

Finally, the exception also contradicts the rationale given earlier in
the same Item.
@AssortedBits
Copy link

AssortedBits commented Mar 13, 2020

override has one purpose, and one purpose only: Fail compilation if the marked function is not invokable via the vtable through a superclass pointer. The two main uses of this are both about catching nasty, easy-to-make bugs:

  • breakage of the virtual mechanism, because of typos
  • memory leaks (and other nasty problems), when deleting an object through a superclass pointer, because of forgetting to mark the superclass's destructor virtual

Marking destructors override on state-owning subclasses is textbook hygiene that you should all be doing by routine.

@shaneasd
Copy link
Contributor

I believe this was already addressed in #1448 in agreement with your position.

drbenmorgan added a commit to drbenmorgan/Bayeux that referenced this issue Oct 9, 2020
Use override consistently for overriden virtual functions. Use tidy
and ISO C++ Core Guideline C.128 so that destructors are treated
the same way. See:

- http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rh-override
- isocpp/CppCoreGuidelines#1448
- isocpp/CppCoreGuidelines#1446
- isocpp/CppCoreGuidelines#721 (comment)
mlxd added a commit to PennyLaneAI/pennylane-lightning that referenced this issue Dec 21, 2021
mlxd added a commit to PennyLaneAI/pennylane-lightning that referenced this issue Dec 21, 2021
* Add virtual destructors to StateVector classes

* Update changelog

* Remove second virtual

* Follow isocpp/CppCoreGuidelines#721 consensus and use override in dtor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants