Show Lecture.VirtualFunctions as a slide show.
CS253 Virtual Functions
Pointers
class Base {
public:
void foo() { cout << "β\n"; }
};
class Derived : public Base {
public:
void foo() { cout << "Δ\n"; }
};
Derived d;
Base *b = &d;
b->foo();
β
b->foo()
called the .foo()
method of Base
. Why?
Because b
is a Base *
.
References
class Base {
public:
void foo() { cout << "β\n"; }
};
class Derived : public Base {
public:
void foo() { cout << "Δ\n"; }
};
Derived d;
Base &b = d;
b.foo();
β
Same result with a reference. References are just a variety of pointers,
so that’s expected.
Unsurprising
These results are unsurprising. If your pointer is a Generic *
,
then p->method()
calls a method of Generic
.
But what if that’s not what you want? What if you have a pointer of
type Generic *
, but it’s really pointing to an object of type
Specific
? It can do that, if Specific
is a subclass (derived
class) of Generic
. You might want p->method()
to call the
method of the object’s actual type, as opposed to its apparent
type.
Virtual
class Base {
public:
virtual void foo() { cout << "β\n"; }
};
class Derived : public Base {
public:
virtual void foo() { cout << "Δ\n"; }
};
Derived d;
Base *b = &d;
b->foo();
Δ
That’s different!
Virtual
class Base {
public:
virtual void foo() { cout << "β\n"; }
};
class Derived : public Base {
public:
virtual void foo() { cout << "Δ\n"; }
};
Derived d;
Base &b = d;
b.foo();
Δ
That’s different!
Terminology
People often call these “virtual functions”, though a good O-O
programmer might call them “virtual methods”. A method is
a function, after all.
Not using virtual
methods:
- Static dispatch
- Compile-time dispatch
Using virtual
methods:
- Dynamic dispatch
- Run-time dispatch
Cost
There is, of course, a cost to all of this.
- A class which uses any
virtual
methods has to contain a pointer
to a virtual function table, which increases the size of the
objects.
- A call to a
virtual
method is slightly slower than a non-virtual
method, because it has to go through a pointer. This can stall
pipelining.
- A call to a
virtual
method can’t be inlined, because the compiler
doesn’t know which method will actually get called.
Sizes
class Sherlock {
public:
void foo() { } // non-virtual
char c;
};
class Watson {
public:
virtual void foo() { } // virtual
char c;
};
cout << sizeof(Sherlock) << ' ' << sizeof(Watson) << '\n';
1 16
Watson has a virtual
function, so each object has an additional pointer.
Then there’s alignment.
Virtual Dtor
class Base {
public:
virtual void foo() { cout << "β\n"; }
};
class Derived : public Base {
public:
virtual void foo() { cout << "Δ\n"; }
string s; // Look--a data member!
};
Base *b = new Derived;
b->foo();
delete b;
c.cc:14: warning: deleting object of polymorphic class type 'main()::Base'
which has non-virtual destructor might cause undefined behavior
Δ
Contamination
If a method is declared virtual
in the base class, it’s
automatically virtual
in all derived classes. It’s virtual
all the way down.
However, I generally repeat the virtual
declaration in each
subclass to remind the reader.
Example
Here’s some ugly syntax.
Since Super
contains a pure virtual function, it cannot be
instantiated. It is an Abstract Base Class,
or ABC—similar to an interface in Java.
class Super {
public:
virtual void foo() = 0;
};
Super s;
c.cc:6: error: cannot declare variable 's' to be of abstract type
'main()::Super'
Example
class Super {
public:
virtual void foo() = 0;
};
class Sub : public Super {
public:
void foo() {
clog << "Aquaman or the Sub-Mariner? You decide!\n"
<< "🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟\n";
}
};
Sub s;
s.foo();
Aquaman or the Sub-Mariner? You decide!
🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟 🐟
Sub
has no pure virtual
functions remaining, so it can be instantiated.
C++ ≠ Java
class Parent {
public String who() { return "parent"; }
}
class Child extends Parent {
public String who() { return "child"; }
}
public class Code {
public static void main(String args[]) {
Parent p = new Child();
System.out.println(p.who());
}
}
child
All Java methods act like C++ virtual
methods.