What is wrong with the following class declarations?
class Base { public: virtual void Foo(); virtual void Bar(); }; class Derived : public Base { public: virtual void Foo(); void Bar(); };
It turns out that the correct definition is the one to Bar, which came as a surprise to me.
While reading more about the new override and final keywords in C++11, I saw an off-the-cuff comment next to a method declaration about a “redundant explicit virtual.” That got me to wondering. I was always under the impression that you were required to declare functions as explicitly virtual through the entire class hierarchy, and when you stopped declaring it as virtual, it was an implicit “final” declaration. Turns out I was wrong!
According to the C++11 specification about virtual function declarations (7.1.2 Clause 5): “The virtual specifier shall be used only in the initial declaration of a non-static class member function.”
That means that once a function is declared as being virtual, it is virtual always. Which makes a considerable amount of sense given the way virtual functions are implemented. So my assumption that declaring an overridden function as being non-virtual made it final was wrong. Now in C++11, you can achieve the goal I was going for by using the final keyword, like this:
void Bar() final;
Thankfully, using redundant virtual specifiers is not illegal, as footnote 109 discusses: “The use of the virtual specifier in the declaration of an overriding function is legal but redundant (has empty semantics).”
You can teach an old dog new tricks. After reading through the specification about virtual functions, I learned that some of my other assumptions were wrong too. For instance:
- You can declare a function as being pure virtual, and then turn around and define it in the same class. It doesn’t even have to produce a warning! (10.3 Clause 9)
- The overriding function can have a covariant return type and still be a valid override. This means the return types do not have to match exactly (though the cv qualifiers must match). (10.3 Clause 5)
Virtual functions are one of the more powerful features of C++, and so I’m really glad I read that glib comment as it’s helped me to learn some new things. Perhaps you’ve learned something new as well!
Hi Aaron,
Would you be willing to concede that maybe both are correct, instead of saying the one with the redundant “virtual” keyword is actually wrong?
I usually put the redundant keyword there as documentation – although of course there is the danger that if the base class’s virtual classification is removed, now the derived’s still going to be virtual anyway. Probably not what was intended, but I guess that ship sails both ways.
My code tends to be a little more verbose when I’m working with newer developers – for example, I’ll use “private” inside a class definition even when not necessary[*], I always explicitly specify the type of inheritance (almost always public), even with structs, etc. Main reason is that sometimes I’m working with developers who benefit from seeing these things being said explicitly, even if it seems like it’s coddling them a little bit.
The C++11 “final”, “default” and “delete” keywords are IMO are a great improvement to the readability – they say “I want to do THIS.”
One other thing – you mentioned that there was a comment about the “redundant explicit virtual” – almost complaining about the unnecessary use of it. Quite a while ago – at LEAST 10 years ago – I used a static analysis tool (or maybe it was even a compiler?) that did just the opposite – it gave a warning when the redundant virtual keyword *wasn’t* used in the derived classes – something to the effect “Warning: derived class function “foo” is virtual but not labeled as such” – implying that anyone who omits the virtual keyword might think the function is no longer virtual.
This behavior, and its nefarious cousin the “hidden nonvirtual base class function(s) due to definition in derived class”, are problems that once you get burned by them, you never make that mistake twice.
[*] I usually DO need to use “private:” inside class definitions anyway, since I normally put the public (interface) part of the class definition first.
Sidenote: interestingly, 2 of the items you mentioned (redundant “virtual” keyword & covariant return types) each merit their own small chapter in Stephen Dewhurst’s book “C++ Common Knowledge”. Item 31 – “Covariant Return Types” – was news to me when I read it. And item 63 – “Optional Keywords” – states “A common misassumption is that omitting the virtual keyword will prevent further overriding in more derived classes.” The book is very good but its title is misleading/confusing… the back of the book describes it as “covering essential but commonly misunderstood topics in C++ programming and design”. A better title would have been “Commonly Misunderstood C++ Behavior” but then that’s too close to the title of another Dewhurst book, “C++ Gotchas”.
@Dan — I agree that it’s more documentation to put the virtual keyword on the derived class function. And honestly, I don’t think the problem has so much to do with the virtual function being made non-virtual (or removed entirely) in the base class. I think the bigger issue has to do with method signatures. Since you can work with covariant return types, as well as polymorphic parameters, there’s a chance that you are not doing what you intend — you may not be overriding the base class function, but instead declaring a new virtual function. But, then again, leaving out the virtual keyword doesn’t help that situation too much either!
I saw the comment on Bjarne’s C++0x page when discussing the new final and overrides keywords. The part that caught my eye was:
But I tend to have the same convention as you: be as explicit as you can with your code, because it makes it easier for people to read. They don’t have to think “what neat trick makes this work” when you’re being more explicit. That’s why I really dig the overrides and final keywords in theory (haven’t had the chance to put them into practice yet due to lack of compiler support).
So is the redundant virtual keyword wrong? Probably not, given that it has no ill-effect on the program and is basically just restating the default. But it also may be a sign of not understanding the way the virtual keyword works (such as in my case!).