-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Comments
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. |
The semantics for virtual destructor is different from "normal" virtual functions. You never override a destructor -- virtual destructors are "chained". |
@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:
Post refactor:
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 |
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 For reference, this is not a bug in Clang Tidy. As can be seen here |
@MikeGitb |
@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. |
@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. |
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.
@gdr-at-ms "You never override a destructor -- virtual destructors are "chained"." is wrong. According to [class.virtual]p6: |
(and, as mentioned before, the "chaining" aspect is there, but is completely orthogonal to overriding) |
No, the chaining aspect is not orthogonal at all. It explains why the tool's insistence of your using 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 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1827.htm Every single time a tool makes the suggestion of adding |
At first I would have also disagreed with @gdr-at-ms but then I remembered C.127 In short as long something will check for rule C.127 there is no need for |
Clang Tidy disagrees wth C.127 as well in a way. Basically, Clang Tidy forces the use of I understand the original intent of |
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 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 |
@gdr-at-ms Could you please explain how do you arrive at the conclusion that
? I see two cases:
So how/why/when is The only argument against using
is misplaced because it directly contradicts the rationale given in its own "Reason" section just above. |
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
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
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
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
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
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.
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.
Marking destructors |
I believe this was already addressed in #1448 in agreement with your position. |
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)
* Add virtual destructors to StateVector classes * Update changelog * Remove second virtual * Follow isocpp/CppCoreGuidelines#721 consensus and use override in dtor
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).
The text was updated successfully, but these errors were encountered: