3

Is it actually necessary to use either virtual or override?

I'm aware that there are a lot of questions on this general topic, e.g.:

From those, and others marked as duplicates (the answers to a lot of the "duplicates" have contained distinct information that was new at least to me), I've learned some things (and, I think, roughly why they're true): override without virtual will not compile. Virtual without override will compile, but if you've made a mistake and don't have your method signature correct, the compiler won't tell you.

But what's going on if I omit both? Example:

struct BaseClass {
    int a_number() {
        return 1;
    }
};
struct DerivedClass : BaseClass {
    int a_number() {
        return 2;
    }
};

This compiles, and a_number returns the appropriate result whether I instantiate BaseClass or DerivedClass. So it behaves as though I have overridden the function. Is there some reason why the code above is wrong? Is it a backwards-compatibility issue, or something more?

My apologies if this is answered directly in one of the related questions here that I missed, and thanks.

EDIT: StackOverflow keeps pointing me to the C++ “virtual” keyword for functions in derived classes. Is it necessary? question, as Wyck did below. I do not think it addresses my question, for the reason I gave him/her in the comments, which, since they are transitory, I'll repeat here: "I'm specifically asking about virtual in superclass methods, while that post seems to be about virtual in subclasses and how it propagates. The accepted answer below answers my question, but I don't think your link does (it might answer the question for someone more experienced in C/C++, but I don't think it answers it for a novice coming from Python and Java, like me)."

The point: I think the questions are related, but not the same.

I've accepted selbie's answer, since it was the first full-fledged "Answer" that answered my question. Wyck's answer provided a lot of useful, more general information.

TCF
  • 103
  • 6
  • 1
    You got the "right answer" because the compiler relied on the static type of the object. If you had a pointer to base class and you tried to call that function on object that were of the derived class you'd get the base class result (to get the right result you'd need virtual). [See here](http://coliru.stacked-crooked.com/a/7d615db12737e064) – Borgleader Dec 28 '19 at 04:15
  • 1
    Does this answer your question? [C++ "virtual" keyword for functions in derived classes. Is it necessary?](https://stackoverflow.com/questions/4895294/c-virtual-keyword-for-functions-in-derived-classes-is-it-necessary) – Wyck Dec 28 '19 at 04:41
  • @Wyck Maybe I am not reading it carefully enough, but I don't think so. I'm specifically asking about `virtual` in superclass methods, while that post seems to be about `virtual` in subclasses and how it propagates. The accepted answer below answers my question, but I don't think your link does (it might answer the question for someone more experienced in C/C++, but I don't think it answers it for a novice coming from Python and Java, like me). – TCF Dec 28 '19 at 04:58

2 Answers2

5

As you have it declared (no virtual methods), Without BaseClass::a_number being declared virtual, an instance of DerivedClass when casted back as BaseClass will not invoke the implementation in the DerivedClass

Example:

BaseClass* instance1 = new DerivedClass();
instance1->a_number();  // returns "1", even though the object is really an instance of Derived

If BaseClass had been declared as follows:

struct BaseClass {
    virtual int a_number() {
        return 1;
    }
};

Then the following code works as you might expect

BaseClass* instance2 = new DerivedClass();
instance2->a_number();  // returns "2", virtual method invocation

The override keyword is optional, but recommended in DerivedClass:

struct DerivedClass : BaseClass {
    int a_number() override {
        return 2;
    }
};

As you have already observed, override doesn't change the program behavior, but if a_number hadn't been declared identically in BaseClass, the compiler will issue an error. It's useful for catching typo errors.

selbie
  • 100,020
  • 15
  • 103
  • 173
4

Static polymorphism (where the method to be called can be determined at compile time) doesn't need the virtual keyword. Whereas Dynamic polymorphism (where the method to be called must be determined at run-time based on the concrete type of the object instance in question) does need the virtual keyword.

Here's one way to think of whether you need dynamic polymorphism or not: What sound does a Cat make: Meow! What sound does a Dog make: Woof! What sound does an Animal make? You don't know because "Animal" isn't specific enough to know what sound it makes. You need dynamic polymorphism in this case.

Without virtual methods you're saying that you know how to implement methods about the base class. In your case, you've provided a non-virtual implementation of a_number that's good for any BaseClass. You've said it's 1. Which means the question of "what is your a_number" can now be answered in the case where you don't know what type the derived class is. (I suspect you will be surprised to learn this is the case.) Only if you happen to know you are dealing with a derived type would you end up calling the derived type's a_number implementation. In other words, the compiler would have to have some way of knowing to call DerivedClass::a_number. The more natural thing (that would likely meet your expectations) is to have a virtual method so that the derived class's implementation is used instead of the base class's implementation even when treated as a base class object.

The magic of how you can treat an object as a base class type but still call the derived class's implemenation at run-time is that an object has a vtable, which you can go learn about.

A non-virtual (static polymorphism) method would be silly for the Animal sounds case. When you ask an animal to Speak it should be a virtual function, because we expect the derived classes to provide different implementations, but we want to be able to ask any Animal to make a sound.

But there are cases where static polymorphism is sufficient and the virtual keyword is not needed. But it's always the case where the behaviour doesn't change in derived classes.

Wyck
  • 10,311
  • 6
  • 39
  • 60
  • This answer was very helpful overall (the one exception was in the last sentence, where it took me a few readings to say for certain what the pronoun "it's" referred to; if I'm right, "it's" could be replaced by "static polymorphism is sufficient is"). I have one remaining question (for now at least): It seems that dynamic polymorphism will always work, but the use of vtables seems to incur some runtime overhead. Is it generally considered good practice to take advantage of static polymorphism where it works? – TCF Jan 01 '20 at 02:58