Introduction
I use three different OO languages all the time, and all of them obviously support inheritance and late binding or virtual functions. I thought a nice article might also be a good reminder to myself when I switch between them about how Java, C# and C++ differ in terms of their syntax and default behaviour.
Background
I am constantly swapping between C++ for the Frostbite video games engine I work on and C# for the tools and web services. I also use Java for my Android home projects (the last one was an ant nest simulator for Swarm Ibtelligence - coming soon ?)
So I thought I would note to myself, the main differences between using the three languages in terms of inheritance and function over-riding (or virtual functions).
Using the Code
Accessibility
C++ supports public
, protected
and private
inheritance. But whatever way it's done, don't forget that anything private
is always private
and does not become accessible to any inheriting or deriving class.
Java simplified this and only allows public
inheritance.
C# copied Java in this respect.
It is generally agreed that you can achieve protected
and private
inheritance by using encapsulation and other existing features in OO languages, without the need for these other complex permutations.
Multiple Inheritance
Now a little bit about multiple inheritance. When building class hierarchies, C++ uses just a colon for inheritance, and following that can be a comma separated list of other base classes. C++ is the only language that supports multiple inheritance, meaning things like the deadly diamond of death can occur if you are not careful. Basically, the DDD is a design where more than one instance of the same class type is part of the object. Imagine class Dog
derives from classes Animal
and Pet
, and both of these derive from a class called Printable
, the instance of Dog
now has two instances of the base class Printable
. So what happens when you change the attributes of this base class type in the dog class ? Does it change instance 1 or instance 2's attributes etc.To avoid this type of complexity in hierarchies (and we normally favor composition over inheritance anyway - combinatorial explosion being one reason), C# and Java both chose not to support MI.
Interfaces
Years ago in the land where C++ was king, you could use an abstract
base class (a class that cannot be instantiated, as it contains at least one pure virtual
function) and put all your pure virtual
functions in there to define a contract. This meant you could force a class deriving from your class to implement these functions. This was the birth of the interface. In C# and Java you can implement these as interfaces, thus giving you a kind of multiple inheritance which is basically a contract only (or function signatures that you must implement).
C# supports both interfaces (which are similar to abstract
base classes in C++, class containing all pure virtual
functions), and abstract
classes which can have one or more pure virtual
functions, and also some functions with actual implementation. Remember all functions inside interface
and abstract
classes must be public,
this is so they can be accessed in calling classes, there would not be much point in having a private function on an interface, who could call it ? There is no implementation in the class so it could not call it's own function ! So, C# covers all thee abstract
, interface
and concrete
classes.
Let's take a look at some code that declares classes in all three languages with inheritance and interfaces:
C++
class derived : public base, base2
Java
class Derived extends Base implements printable
C#
public interface printable
public abstract class baser
public class derived : baser, printable
Virtual Functions
Another apsect of object orientated code is polymorphism. The idea is a type can be constructed at run time, at compile type we only know the hierarchy it will be part of, ie we have a base type pointer, the ninstantiate a derived class in the hierarchy. Using this late-binding enables us to call fucntions that are decided at run-time not compile time. All three languages support late-binding and therefore virtual
functions. Let's talk about each one.
C++
The old wise one by default has its functions set to non virtual. That is, if you have a base and a derived class, both of which have a function called print()
, then it doesn't matter what type of instance you have constructed, you will call the function on the class depending on what type your reference or pointer is, not what you are pointing at. So to use virtual
functions, you must declare a function virtual
(anywhere in the hierarchy is fine), this tells the compiler to create a virtual function table, and to look up the address of the function in this table depending on what type you are when running. Once the function is marked virtual
, the instance type will be the deciding factor as to which version of the funciton you call, not the type of the reference or pointer.
Java
Now Java went super simple, everything is virtual
, no need for any statements to tell the run time engine to make something virtual, if a derived class and base class both declare a function called Print
, with the same signature, then the instance type will decide which one the JRE uses. Super simple! That is of course unless you don't want this feature (maybe security is an issue ie you dont want the void AddMoneyToBankAccount() function to be overridden), in which case you mark the function final
, and nothing can override it then, damn !
C#
So lastly C#, all functions in this saloon here are non-virtual, and not only that, if you want to override a function, it must be marked as virtual
in the base, and the overriding function must be marked as override
. You can however, have two functions with the same signature, in a related hierarchy, and not mark them with override
, you just mark the derived one with new
. You are then explicitly telling the compiler that this is intentional, you want to hide the base functions implementation, and it is not a virtual function. so if you call the function with a Base pointer, it will always call the base pointer class types function, even if you point it at an instance of derived, look at it like the function is statically linked by the compiler, not dynamically by the run time (even though its C# so there is no compiler per se).
And finally some code to demonstrate all the virtual and overriding behaviour!!
C++ Code
#include "stdafx.h"
#include <iostream>
using namespace std;
class base
{
public:
void hello()
{
cout << "Base func non-virtual" << endl;
}
virtual void hello2()
{
cout <<"Base func virtual"<<endl;
}
};
class derived : public base {
public:
void hello()
{
cout << "Derived func non-virtual" << endl;
}
virtual void hello2()
{
cout <<"Derived func virtual"<<endl;
}
};
int main()
{
cout << "calling function defined in both base and der that is NOT virtual,
then calling a function that is virtual" << endl;
{
cout << "Instance is Derived - Reference is Derived" << endl;
derived d;
d.hello();
d.hello2();
}
{
cout << "Instance is Derived - Reference is Base" << endl;
base *f = new derived ;
f->hello();
f->hello2();
}
{
cout << "Instance is Base - Reference is Base" << endl;
base t;
t.hello();
t.hello2();
}
{
cout << "Instance is Derived - reinterpret cast to base - Reference is Derived" << endl;
derived *g = new derived ;
base *j = reinterpret_cast<base*>(g);
j->hello();
j->hello2();
}
getchar();
return 0;
}
And the output:
calling function defined in both base and der that is NOT virtual,
then calling a function that is virtual
Instance is Derived - Reference is Derived
Derived func non-virtual
Derived func virtual
Instance is Derived - Reference is Base
Base func non-virtual
Derived func virtual
Instance is Base - Reference is Base
Base func non-virtual
Base func virtual
Instance is Derived - reinterpret cast to base - Reference is Derived
Base func non-virtual
Derived func virtual
And C#
using System;
namespace ConsoleApplication2
{
public class based
{
public void hello()
{
Console.WriteLine("hello on base instance - non virtual on base");
Console.WriteLine("func is - non virtual on deriv");
}
public virtual void hello2()
{
Console.WriteLine("hello2 on base instance - virtual on base");
Console.WriteLine("func is - virtual on deriv");
}
public void hello3()
{
Console.WriteLine("hello3 on base instance - non virtual on base");
Console.WriteLine("func is new and virtual on deriv");
}
public virtual void hello4()
{
Console.WriteLine("hello4 on base instance - virtual on base");
Console.WriteLine("func is new and virtual on deriv");
}
public virtual void hello5()
{
Console.WriteLine("hello5 on base instance - virtual on base");
Console.WriteLine("func is override on deriv");
}
public void hello6()
{
Console.WriteLine("hello6 on base instance - NOT virtual on base");
Console.WriteLine("func is override on deriv");
}
}
public class derived : based
{
public void hello()
{
Console.WriteLine("hello on deriv - non virtual on deriv");
Console.WriteLine("func is non virtual on base");
}
public virtual void hello2()
{
Console.WriteLine("hello2 on deriv - virtual on deriv");
Console.WriteLine("func is virtual on base");
}
public new virtual void hello3()
{
Console.WriteLine("hello3 on deriv - new and virtual on deriv");
Console.WriteLine("func is non virtual on base");
}
public new virtual void hello4()
{
Console.WriteLine("hello4 on deriv - new and virtual on deriv");
Console.WriteLine("func is virtual on base");
}
public override void hello5()
{
Console.WriteLine("hello5 on deriv - override on deriv");
Console.WriteLine("func is virtual on base");
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("base ref with base inst");
based b = new based();
b.hello();
b.hello2();
b.hello3();
b.hello4();
b.hello5();
Console.WriteLine();
Console.WriteLine("deriv ref with deriv inst");
derived d = new derived();
d.hello();
d.hello2();
d.hello3();
d.hello4();
d.hello5();
Console.WriteLine();
Console.WriteLine("base ref with deriv inst");
based o = new derived();
o.hello();
o.hello2();
o.hello3();
o.hello4();
o.hello5();
Console.WriteLine();
Console.WriteLine("deriv ref with deriv inst cast back to base ref");
derived s = new derived();
based ds = s;
ds.hello();
ds.hello2();
ds.hello3();
ds.hello4();
ds.hello5();
Console.WriteLine("The end--");
Console.ReadLine();
}
}
}
And the output:
base ref with base inst
hello on base instance - non virtual on base
func is - non virtual on deriv
hello2 on base instance - virtual on base
func is - virtual on deriv
hello3 on base instance - non virtual on base
func is new and virtual on deriv
hello4 on base instance - virtual on base
func is new and virtual on deriv
hello5 on base instance - virtual on base
func is override on deriv
deriv ref with deriv inst
hello on deriv - non virtual on deriv
func is non virtual on base
hello2 on deriv - virtual on deriv
func is virtual on base
hello3 on deriv - new and virtual on deriv
func is non virtual on base
hello4 on deriv - new and virtual on deriv
func is virtual on base
hello5 on deriv - override on deriv
func is virtual on base
base ref with deriv inst
hello on base instance - non virtual on base
func is - non virtual on deriv
hello2 on base instance - virtual on base
func is - virtual on deriv
hello3 on base instance - non virtual on base
func is new and virtual on deriv
hello4 on base instance - virtual on base
func is new and virtual on deriv
hello5 on deriv - override on deriv
func is virtual on base
deriv ref with deriv inst cast back to base ref
hello on base instance - non virtual on base
func is - non virtual on deriv
hello2 on base instance - virtual on base
func is - virtual on deriv
hello3 on base instance - non virtual on base
func is new and virtual on deriv
hello4 on base instance - virtual on base
func is new and virtual on deriv
hello5 on deriv - override on deriv
func is virtual on base
The end--
And lastly Java
package com.roboticsfordreamers;
class Base implements
{
public void Hello()
{
System.out.println("Hello this the base");
}
}
class Derived extends Base
{
public void Hello()
{
System.out.println("Hello this the derived");
}
}
public class Main {
public static void main(String[] args) {
Base b = new Base();
Derived d = new Derived();
Base b2 = new Derived();
b.Hello();
d.Hello();
b2.Hello();
}
}
And the output :
Hello this the base
Hello this the derived
Hello this the derived
Process finished with exit code 0
I hope you find this a useful comparison and reference.