Is it possible to have the observed behavior of a program changed by simply adding a new virtual function to a base class? I mean that no other change must be made to the code.
-
2Of course. The list of possible ways to break existing code, in C++, is an endless list. – Sam Varshavchik Jul 29 '16 at 11:23
-
@SamVarshavchik This question and answer is an illustration [requested elsewhere](http://stackoverflow.com/questions/38656443/what-is-the-opposite-of-c-override-final-specifier/38656692#comment64694369_38656558). – Leon Jul 29 '16 at 11:25
-
1One example is if you add a pure virtual function. No implementation in the instantiated derived class(es) will break compilation. – aichao Jul 29 '16 at 11:29
-
Related: [What is the opposite of c++ override / final specifier?](http://stackoverflow.com/questions/38656443/what-is-the-opposite-of-c-override-final-specifier/38656968#38656558) – iammilind Jul 29 '16 at 12:20
4 Answers
The following program prints OK. Uncomment the virtual function in B
and it will start printing CRASH!.
#include <iostream>
struct B
{
//virtual void bar() {}
};
struct D : B
{
void foo() { bar(); }
void bar() { std::cout << "OK" << std::endl; }
};
struct DD : D
{
void bar() { std::cout << "CRASH!" << std::endl; }
};
int main()
{
DD d;
d.foo();
return 0;
}
The problem is that after a virtual function B::bar()
is introduced the binding of the call to bar()
in D::foo()
changes from static to dynamic.

- 31,443
- 4
- 72
- 97
-
2But this is the purpose of `virtual` functions in the first place.... There is nothing surprising here :-). As far as you have a function signature in a derived class that is same, but `virtual` as in one of the base(s), it is automatically an override. Mistakes from slightly different signatures is partly what brought about the `override` keyword – WhiZTiM Jul 29 '16 at 11:22
-
@WhiZTiM This question and answer is an illustration [requested elsewhere](http://stackoverflow.com/questions/38656443/what-is-the-opposite-of-c-override-final-specifier/38656692#comment64694369_38656558). – Leon Jul 29 '16 at 11:24
-
1@WhiZTiM: No there is nothing surprising, but it *does* provide an example to prove that introducing a virtual function in a base class can break previous working code (which is the question that the OP asked). – Martin Bonner supports Monica Jul 29 '16 at 11:43
-
1I understand. Good point... Upvoted. :-). But @Leon, I think you should also modify your answer to point out binary incompatibility of modifying a base class. – WhiZTiM Jul 29 '16 at 11:45
Binary incompatibility.
If you have an externally loadable module (i.e a DLL) then which uses the old definition of the base class you will have problems. Or if the loader program have the old definition and the DLL have the new it's the same problem. This is also a problem if you for some reason save objects in files using raw binary copying (not any kind of serialization).
This have nothing to do with with the C++ specification of virtual functions, but how most compilers implement them.
Generally speaking, if the "interface" of a class changes (base class or not) then you should recompile everything which uses that class.

- 5,146
- 1
- 41
- 76

- 400,186
- 35
- 402
- 621
-
It's worse than that, see the answer from Leon - which doesn't require anything outside the C++ standard. – Martin Bonner supports Monica Jul 29 '16 at 11:48
When the API is changed in a backwards incompatible manner, the code that depends on the earlier version of the API is no longer guaranteed to work.
All derived classes depend on the API of their base classes.
Addition of a virtual function is a backwards incompatible change. Leon's answer shows a fine example of how the API breakage can manifest itself.
Therefore yes, addition of a virtual function can break the program, unless the dependant parts are fixed to work with the new API. This means that whenever a virtual function is added, one should inspect all derived classes, and make sure that the meaning of their respective API has not been changed by the addition.
-
You assert that adding a virtual function is a backwards incompatible change, but you don't provide any evidence of that assertion. Given that the question can be rephrased as "is adding a virtual function to a base class a backwards incompatible change", just saying "yes" is not very helpful. – Martin Bonner supports Monica Jul 29 '16 at 11:47
-
@MartinBonner that change would alter the meaning of the question (a backwards incompatible change, if you will) albeit subtly. I agree, my assertion should be accompanied by an example. Leon has already posted a fine example, and I don't see any need to repeat it. However, I shall refer to it to back up my assertion. – eerorika Jul 29 '16 at 11:56
#include <stdlib.h>
struct A {
#if ADD_TO_BASE
virtual void foo() { }
#endif
};
struct B : A {
void foo() { }
};
struct C : B {
void foo() { abort(); }
};
int main() {
C c;
B& b = c;
b.foo();
}
Without the virtual function the base class b.foo()
is a non-virtual call to B::foo()
:
$ g++ virt.cc
$ ./a.out
With the virtual in the base class it is a virtual call to C::foo()
:
$ g++ virt.cc -DADD_TO_BASE
$ ./a.out
Aborted (core dumped)
You can also get nasty undefined behaviour due to binary incompatibility, because adding a virtual function to a non-polymorphic base class changes its size and layout (requiring all other translation units that use it to be recompiled).
Adding a new virtual function to an already polymorphic base class changes the layout of the vtable, either adding a new entry at the end, or changing the position of other functions if added in the middle (and even if added at the end of the base vtable, that's in the middle of the vtable for any derived classes which add new virtual functions). That means that already compiled code that uses the vtable might end up calling the wrong function, because it uses the wrong slot in the vtable.
The binary incompatibilities can be fixed by recompiling all the relevant code, but silent changes in behaviour like the example at the top can't be fixed simply by recompiling.

- 166,810
- 27
- 341
- 521