how to handle virtual function with cast
TOC
Usages
- Override a function of a parent class
Explain
Dynamic Binding with Virtual Keyword
C++ is a compile based language. A compile is a job to translate source code to binary for machine. When compiler compiles source code, it tries to optimize source code to improve performance. This is the one of reasons why C/C++ is faster than other programming language.
However, compile is a static job. Because a compiler could not know what happens in a run time, it only relies on source code. A pointer for derived class object is a good example. If a pointer is declared for base class but it refers to derived class object, compiler just links it to base class's member.
However, sometimes, something should work in run time. If a programmer wants to use a base class pointer to indicate an actual object, not only for base class object, virtual keyword is necessary. Virtual tells a compiler that the target object of the member is determined in run time. Therefore, a compiler does not link the pointer to a class object statically.
Virtual is very useful for overriding. Overriding is the situation that derived classes have members whose name is the same as base class', but its mechanism is little different. In that case, a pointer for base class is a handler for base and derived classes both. With virtual keyword, the target object is determined in run time.
Code 1 shows a simple example of virtual.
// 01_C_VirtualFunction.cpp
#include <iostream>
using namespace std;
class BaseClass
{
public:
void foo()
{
cout << "BaseClass foo" << endl;
}
virtual void var()
{
cout << "BaseClass var" << endl;
}
};
class DerivedClass : public BaseClass
{
public:
void foo()
{
cout << "DerivedClass foo" << endl;
}
virtual void var()
{
cout << "DerivedClass var" << endl;
}
};
int main()
{
BaseClass b;
DerivedClass d;
BaseClass* p = &d;
b.foo(); // BaseClass foo
d.foo(); // DerivedClass foo
p->foo(); // BaseClass foo
b.var(); // BaseClass var
d.var(); // DerivedClass var
p->var(); // DerivedClass var
return 0;
}
// Compile: clang++ -std=c++14
// -o 01_C_VirtualFunction 01_C_VirtualFunction.cpp
BaseClass foo
DerivedClass foo
BaseClass foo
BaseClass var
DerivedClass var
DerivedClass var
In this example, The type of pointer p is BaseClass, but it refers to an object of DerivedClass. Because run() is a member function without virtual keyword, p->run() indicates run() of base class, BaseClass. However, eat() is with virtual. Therefore, when eat() is called, DerivedClass's eat() is executed, because the target of p is the object of DerivedClass.
Virtual Function Table
When at least one member function is with virtual keyword, a virtual function table is created for its class, and additional pointer variable is located in the class. Code 2 checks the size of class whose one of member functions is with virtual keyword.
// 02_C_VirtualSize.cpp
#include <iostream>
using namespace std;
class BaseClass
{
protected:
int val;
public:
BaseClass(int _val)
: val(_val)
{}
virtual void setVal(int _val)
{
val = _val;
}
virtual int getVal()
{
return val;
}
virtual void print()
{
cout << "BaseClass val: " << val << endl;
}
};
class DerivedClass : public BaseClass
{
protected:
int num;
public:
DerivedClass(int _val, int _num)
: BaseClass(_val)
, num(_num)
{}
void setNum(int _num)
{
num = _num;
}
virtual void print()
{
cout << "DerivedClass val: " << val
<< ", num: " << num << endl;
}
};
int main()
{
BaseClass b(1);
cout << sizeof(b) << endl; // 16
// = 4 (for int val)
// + 4 (for alignment)
// + 8 (for the pointer
// for virtual function table)
DerivedClass d(2, 3);
cout << sizeof(d) << endl; // 16
// = 4 (for int val)
// + 4 (for num)
// + 8 (for the pointer
// for virtual function table)
BaseClass* p = &d;
cout << sizeof(p) << endl; // 8 (size of pointer)
return 0;
}
// Compile: clang++ -std=c++14
// -o 02_C_VirtualSize 02_C_VirtualSize.cpp
16
16
8
The additional 8 byte is from the pointer for virtual function table. Image 1 shows how their memories are.
If base class's virtual function is overrided, the target of virtual function table's element indicates the function of derived class. If not overrided, functions for base class are used. In this example, only print() is an override function. Therefore, the third element of virtual function table of DerivedClass indicates DerivedClass::print(). If a new function is defined without virtual keyword, it is not registered on virtual function table, like setNum().
Dynamic Cast for Virtual Function
C++ provides 4 types of cast, and they have different features. One of the representative differences is how to handle virtual functions in downcasting. Downcasting is to cast a pointer for a base class to a pointer for a derived class.
Static cast and reinterpret cast casts a pointer for a source class to a target class in the compile time. At that time, compiler does not know a an actual type of an object, but only a type of the target class. Therefore, the compiler does not change the pointer of virtual function table. Because, however, Non-virtual functions are determined by the type of the pointer, derived class' non-virtual functions are used.
On the other hands, dynamic_cast checks the real type of the object in the run time, so the pointer for virtual function table can be changed. After casting, it refers to the pointer of virtual function table for the target class.
Code 3 shows how they work for downcasting.
// 03_C_DynamicVirtual.cpp
#include <iostream>
using namespace std;
class BaseClass
{
public:
void foo()
{
cout << "BaseClass foo" << endl;
}
virtual void var()
{
cout << "BaseClass var" << endl;
}
};
class DerivedClass : public BaseClass
{
public:
void foo()
{
cout << "DerivedClass foo" << endl;
}
virtual void var()
{
cout << "DerivedClass var" << endl;
}
};
int main()
{
BaseClass b;
DerivedClass* p = reinterpret_cast(&b);
p->foo(); // DerivedClass foo
p->var(); // BaseClass var
p = static_cast(&b);
p->foo(); // DerivedClass foo
p->var(); // BaseClass var
p = dynamic_cast(&b);
if (p == nullptr)
{
cout << "Dynamic_cast failed" << endl;
p->foo(); // DerivedClass foo
p->var(); // Segmentation fault
}
return 0;
}
// Compile: clang++ -std=c++14
// -o 03_C_DynamicVirtual 03_C_DynamicVirtual.cpp
DerivedClass foo
BaseClass var
DerivedClass foo
BaseClass var
Dynamic_cast failed
DerivedClass foo
Segmentation fault
However, dynamic_cast is not usually used for downcasting, because it can be failed when the type of the pointer and the type of the object are not compatible. If dynamic_cast is failed, it returns nullptr. As a result, a program crashes with segmentation fault at a virtual function.
Reinterpret Cast for Virtual Function
Reinterpret cast is a very powerful cast. It can do any type change. However, it is done in compile time, so it does not change the pointer for virtual function table. Code 4 shows how it works.
// 04_C_ReinterpretVirtual.cpp
#include <iostream>
using namespace std;
class A
{
public:
virtual void foo()
{
cout << "A foo" << endl;
}
};
class B
{
public:
virtual void var()
{
cout << "B var" << endl;
}
virtual void set()
{
cout << "A set" << endl;
}
};
int main()
{
A a;
B* p = reinterpret_cast(&a);
p->var(); // A foo
p->set(); // Segmentation fault
return 0;
}
// Compile: clang++ -std=c++14
// -o 04_C_ReinterpretVirtual 04_C_ReinterpretVirtual.cpp
A foo
By reinterpret_cast, the type is changed, but the pointer for virtual function table is not changed. Because virtual functions in virtual function table are stored with indexes, B's var() calls A's foo(). However, A has only one virtual function, foo(), so B's set() raises the segmentation fault. Image 2 shows how their memories are.
Summary
- Virtual keyword allows dynamic binding(Run Time Binding)
- Virtual creates a virtual function table and its pointer, so the memory size is increased.
- When dynamic_cast is failed, virtual functions leads a segmentation fault.
- When reinterpret_cast is used, virtual functions could lead a segmentation fault.
COMMENTS