Difference between using non-virtual base class functions versus derived class non-implemented virtual functions

1.1k views Asked by At

This question is slightly related to What are the differences between overriding virtual functions and hiding non-virtual functions?, but I'm not asking about the technical details, rather about the usage of non-virtual and virtual functions.

Here's a little background. Let's say I have a base class A and two derived classes B and C

#include <iostream>

class A {
public:
  A() {};
  virtual void foo() { std::cout << "foo() called in A\n"; };
  virtual void bar() { std::cout << "bar() called from A\n"; };
  void xorp() { std::cout << "xorp() called from A\n"; };
  virtual ~A() {};
};

class B : public A {
public:
  B() {};
  virtual ~B() {};
  virtual void foo() override { std::cout << "foo() called in B\n"; };
  //virtual void bar() override not implemented in B, using A::bar();
};

class C : public A {
public:
  C() {};
  virtual ~C() {};
  virtual void foo() override { std::cout << "foo() called in C\n"; };
  //virtual bar() override not implemented in C, using A::bar();
};

int main() {
  A a{};
  B b{};
  C c{};

  a.foo(); //calls A::foo()
  a.bar(); //calls A::bar()
  a.xorp(); //calls non-virtual A::xorp()

  b.foo(); //calls virtual overridden function B::foo()
  b.bar(); //calls virtual non-overridden function A::bar()
  b.xorp(); //calls non-virtual A::xorp()

  c.foo(); //calls virtual overridden function C::foo()
  c.bar(); //calls virtual non-overridden function A::bar()
  c.xorp(); //calls non-virtual A::xorp()

  return 0;
}

This outputs, as expected, the following:

foo() called in A
bar() called from A
xorp() called from A
foo() called in B
bar() called from A
xorp() called from A
foo() called in C
bar() called from A
xorp() called from A

If I leave the virtual function bar() unimplemented in the derived classes, any call to bar() in the derived classes B and C gets resolved to A::bar(). xorp(), which is a non-virtual function, can also be called from the derived classes as either b.xorp() or b.A::xorp().

If I were to implement a xorp() in B, for example, it would effectively hide the A::xorp() and a call to b.xorp() would actually be a call to b.B::xorp().

Which brings me to my question, using the example above. Let's say I have a helper function that the derived classes need for their implementation.

Is there a difference between having the helper function be a non-virtual member function (like xorp()), versus the helper function being a virtual function that the derived classes do not override (bar())?

Reading through this presentation on class object layout and VTABLEs (https://www.cs.bgu.ac.il/~asharf/SPL/Inheritance.pptx, slides 28-35) I could not really spot a difference, since both non-virtual and non-overridden virtual functions point to the same place (i.e the function in the base class)

Could anyone give me an example where these two approaches would produce different results, or if there is a caveat that I have not spotted?

2

There are 2 answers

3
StoryTeller - Unslander Monica On BEST ANSWER

The flaw in your example is that you aren't using polymorphism. You operate on all the objects directly. You won't notice anything related to overriding, because none of the calls need to be resolved dynamically. And if the call is not resolved dynamically, there is absolutely no difference between virtual functions and non-virtual ones. To see the difference, use a helper free function:

void baz(A& a) {
  a.foo();
  a.bar();
  a.xorp();
}

int main() {
  // As before
  baz(a);
  baz(b);
  baz(c);
}

Now you should be able to see a noticeable difference in how the calls to foo, bar and baz are resolved. In particular...

If I were to implement a xorp() in B, for example, it would effectively hide the A::xorp() and a call to b.xorp() would actually be a call to b.B::xorp().

... will no longer be true inside baz.

0
HostileFork says dont trust SE On

Is there a difference between having the helper function be a non-virtual member function (like xorp()), versus the helper function being a virtual function that the derived classes do not override (bar())?

If you mark a method virtual but you never override it, it's behavior will be equivalent to if you'd never marked it virtual. The relationship to other methods it calls in the object is not affected.

That isn't to say there aren't still "differences".

It certainly conveys a difference in intent to those reading the code. If your xorp() method is not virtual--but relies on virtual methods to implement its behavior--then people will understand "xorpiness" as having certain fixed properties. They would likely try to avoid a redefinition of xorp() in any derived classes, and know to only affect the xorpiness indirectly through defining the virtual methods it depends on.

Plus, the compiler can't always know if you're going to use a virtual override or not. So it can't optimize out the extra code for virtual dispatch--even if you aren't "taking advantage" of it. (Occasionally it can, like if you have a class you never derive from and don't export it...the virtual may just get dropped.)

And with exported classes you expect other people to use: just because you never overrode a method doesn't mean someone else won't. Unless you use final and prevent derivation of your objects (which isn't considered terribly friendly to do, unless you've got really good reasons). So if you make something virtual, the ability is there, and once someone else adds an override then yes--there will be a difference at that point.