Professional Documents
Culture Documents
virtual table. The virtual table is a lookup table of functions used to resolve function
calls in a dynamic/late binding manner. The virtual table sometimes goes by other
names, such as “vtable”, “virtual function table”, “virtual method table”, or “dispatch
table”.
Because knowing how the virtual table works is not necessary to use virtual functions,
this section can be considered optional reading.
The virtual table is actually quite simple, though it’s a little complex to describe in
words. First, every class that uses virtual functions (or is derived from a class that
uses virtual functions) is given it’s own virtual table. This table is simply a static array
that the compiler sets up at compile time. A virtual table contains one entry for each
virtual function that can be called by objects of the class. Each entry in this table is
simply a function pointer that points to the most-derived function accessible by that
class.
Second, the compiler also adds a hidden pointer to the base class, which we will call
*__vptr. *__vptr is set (automatically) when a class instance is created so that it
points to the virtual table for that class. Unlike the *this pointer, which is actually a
function parameter used by the compiler to resolve self-references, *__vptr is a real
pointer. Consequently, it makes each class object allocated bigger by the size of one
pointer. It also means that *__vptr is inherited by derived classes, which is important.
By now, you’re probably confused as to how these things all fit together, so let’s take
a look at a simple example:
class Base
{
public:
virtual void function1() {};
virtual void function2() {};
};
Because there are 3 classes here, the compiler will set up 3 virtual tables: one for
Base, one for D1, and one for D2.
The compiler also adds a hidden pointer to the most base class that uses virtual
functions. Although the compiler does this automatically, we’ll put it in the next
example just to show where it’s added:
class Base
{
public:
FunctionPointer *__vptr;
virtual void function1() {};
virtual void function2() {};
};
When a class object is created, *__vptr is set to point to the virtual table for that class.
For example, when a object of type Base is created, *__vptr is set to point to the
virtual table for Base. When objects of type D1 or D2 are constructed, *__vptr is set
to point to the virtual table for D1 or D2 respectively.
Now, let’s talk about how these virtual tables are filled out. Because there are only
two virtual functions here, each virtual table will have two entries (one for
function1(), and one for function2()). Remember that when these virtual tables are
filled out, each entry is filled out with the most-derived function an object of that class
type can call.
Base’s virtual table is simple. An object of type Base can only access the members of
Base. Base has no access to D1 or D2 functions. Consequently, the entry for function1
points to Base::function1(), and the entry for function2 points to Base::function2().
D1’s virtual table is slightly more complex. An object of type D1 can access members
of both D1 and Base. However, D1 has overridden function1(), making
D1::function1() more derived than Base::function1(). Consequently, the entry for
function1 points to D1::function1(). D1 hasn’t overridden function2(), so the entry for
function2 will point to Base::function2().
D2’s virtual table is similar to D1, except the entry for function1 points to
Base::function1(), and the entry for function2 points to D2::function2().
int main()
{
D1 cClass;
}
Because cClass is a D1 object, cClass has it’s *__vptr set to the D1 virtual table.
int main()
{
D1 cClass;
Base *pClass = &cClass;
}
Note that because pClass is a base pointer, it only points to the Base portion of cClass.
However, also note that *__vptr is in the Base portion of the class, so pClass has
access to this pointer. Finally, note that pClass->__vptr points to the D1 virtual table!
Consequently, even though pClass is of type Base, it still has access to D1’s virtual
table.
int main()
{
D1 cClass;
Base *pClass = &cClass;
pClass->function1();
}
First, the program recognizes that function1() is a virtual function. Second, uses
pClass->__vptr(as vptr now points to D1 virtual table when D1’s object got created)
to get to D1’s virtual table. Third, it looks up which version of function1() to call in
D1’s virtual table. This has been set to D1::function1(). Therefore, pClass-
>function1() resolves to D1::function1()!
Now, you might be saying, “But what if Base really pointed to a Base object instead
of a D1 object. Would it still call D1::function1()?”. The answer is no.
int main()
{
Base cClass;
Base *pClass = &cClass;
pClass->function1();
}
In this case, when cClass is created, __vptr points to Base’s virtual table, not D1’s
virtual table. Consequently, pClass->__vptr will also be pointing to Base’s virtual
table. Base’s virtual table entry for function1() points to Base::function1(). Thus,
pClass->function1() resolves to Base::function1(), which is the most-derived version
of function1() that a Base object should be able to call.
By using these tables, the compiler and program are able to ensure function calls
resolve to the appropriate virtual function, even if you’re only using a pointer or
reference to a base class!
Calling a virtual function is slower than calling a non-virtual function for a couple of
reasons: First, we have to use the *__vptr to get to the appropriate virtual table.
Second, we have to index the virtual table to find the correct function to call. Only
then can we call the function. As a result, we have to do 3 operations to find the
function to call, as opposed to 2 operations for a normal indirect function call, or one
operation for a direct function call. However, with modern computers, this added time
is usually fairly insignificant.
Virtual inheritance
In the C++ programming language, virtual inheritance is a kind of inheritance that
can be used under multiple inheritance. When a class C has a direct or indirect base
class X, it may inherit it indirectly through several inheritance lines. All the virtual
occurrences of X will result in a single internal instance of X within an instance of C.
This can be used to solve some problems (particularly the "diamond problem"), by
clarifying ambiguity over which ancestor class to use. It is used when inheritance is
representing restrictions of a set rather than composition of parts. A multiply-inherited
base class is denoted as virtual with the virtual keyword
The problem
Consider the following class hierarchy.
class Animal
{
public:
virtual void eat();
};
Bat bat;
Similarly, an attempt to directly cast a Bat object to an Animal would fail, since the
cast is ambiguous.
The solution
We can re-declare our classes as follows:
class Animal
{
public:
virtual void eat();
};
The Animal portion of Bat::WingedAnimal is now the same Animal instance as the
one used by Bat::Mammal, which is to say that a Bat has only one, shared, Animal
instance in its representation and so a call to Bat::eat() is unambiguous.
Additionally, a direct cast from Bat to Animal is also unambiguous, now that there
exists only one Animal instance which Bat could be converted to.
• public
• protected
• private
Any of these three inheritance access specifiers can be modified with the virtual
keyword.
The three access modifiers public, protected and private are analogous to the
public
When a base class is specified as public ie: class c : public base {}; the base
class can be seen by anyone who has access to the derived class. That is, any members
protected
When a base class is specified as protected ie: class c : protected base {};
private
When a base class is specified as private ie: class c : private base {}; the
struct X {
public:
void A() {}
};
struct Y {
public:
void B() {}
};
struct Z {
public:
void C() {}
};
struct R : public Q {
public:
void Test2()
{
A(); // OK
B(); // OK
C(); // NOT OK
Q t;
Y *y = &t // OK
Z *z = &t // NOT OK
}
};
R t2;
t2.A(); // OK
t2.B(); // NOT OK
t2.C(); // NOT OK
X *x = &t1; // OK
Y *y = &t1; // NOT OK
Z *z = &t1; // NOT OK
x = &t2; // OK
y = &t2; // NOT OK
z = &t2; // NOT OK
The example given is pretty vague... Use the one below if you like it.
Class A
public:
int apub;
private:
int aprv;
protected:
int apro;
---------------------------------------------------
Public inheritance will inherit protected members,methods of base class with visibilty
modifier as protected.
Public inheritance will inherit public members,methods of base class with visibilty modifier
as public.
Class B : public A
public:
private:
int bprv;
protected:
---------------------------------------------------
Private inheritance will inherit protected as well as public members,methods of base class
Class B : private A
public:
int bpub;
private:
protected:
int bpro;
}
Protected visibility mode during Inheritance
---------------------------------------------------------
Private inheritance will inherit protected as well as public members,methods of base class
Class B : private A
public:
int bpub;
private:
int bprv;
protected:
Friend functions
In principle, private and protected members of a class cannot be accessed from outside
the same class in which they are declared. However, this rule does not affect friends.
If we want to declare an external function as friend of a class, thus allowing this function
to have access to the private and protected members of this class, we do it by declaring a
prototype of this external function within the class, and preceding it with the keyword
friend:
1 // friend functions 24
2 #include <iostream>
3 using namespace std;
4
5 class CRectangle {
6 int width, height;
7 public:
8 void set_values (int, int);
9 int area () {return (width *
10 height);}
11 friend CRectangle duplicate
12 (CRectangle);
13 };
14
15 void CRectangle::set_values (int
16 a, int b) {
17 width = a;
18 height = b;
19 }
20
21 CRectangle duplicate (CRectangle
22 rectparam)
23 {
24 CRectangle rectres;
25 rectres.width =
26 rectparam.width*2;
27 rectres.height =
28 rectparam.height*2;
29 return (rectres);
30 }
31
32 int main () {
CRectangle rect, rectb;
rect.set_values (2,3);
rectb = duplicate (rect);
cout << rectb.area();
return 0;
}
The duplicate function is a friend of CRectangle. From within that function we have
been able to access the members width and height of different objects of type
CRectangle, which are private members. Notice that neither in the declaration of
duplicate() nor in its later use in main() have we considered duplicate a member of
class CRectangle. It isn't! It simply has access to its private and protected members
without being a member.
The friend functions can serve, for example, to conduct operations between two different
classes. Generally, the use of friend functions is out of an object-oriented programming
methodology, so whenever possible it is better to use members of the same class to
perform operations with them. Such as in the previous example, it would have been
shorter to integrate duplicate() within the class CRectangle.
Friend classes
Just as we have the possibility to define a friend function, we can also define a class as
friend of another one, granting that first class access to the protected and private
members of the second one.
1 // friend class 16
2 #include <iostream>
3 using namespace std;
4
5 class CSquare;
6
7 class CRectangle {
8 int width, height;
9 public:
10 int area ()
11 {return (width * height);}
12 void convert (CSquare a);
13 };
14
15 class CSquare {
16 private:
17 int side;
18 public:
19 void set_side (int a)
20 {side=a;}
21 friend class CRectangle;
22 };
23
24 void CRectangle::convert (CSquare
25 a) {
26 width = a.side;
27 height = a.side;
28 }
29
30 int main () {
31 CSquare sqr;
32 CRectangle rect;
33 sqr.set_side(4);
34 rect.convert(sqr);
35 cout << rect.area();
36 return 0;
}
You may also see something new at the beginning of the program: an empty declaration of
class CSquare. This is necessary because within the declaration of CRectangle we refer
to CSquare (as a parameter in convert()). The definition of CSquare is included later,
so if we did not include a previous empty declaration for CSquare this class would not be
visible from within the definition of CRectangle.
Consider that friendships are not corresponded if we do not explicitly specify so. In our
example, CRectangle is considered as a friend class by CSquare, but CRectangle does
not consider CSquare to be a friend, so CRectangle can access the protected and private
members of CSquare but not the reverse way. Of course, we could have declared also
CSquare as friend of CRectangle if we wanted to.
Another property of friendships is that they are not transitive: The friend of a friend is not
considered to be a friend unless explicitly specified.
This could be represented in the world of classes with a class CPolygon from which we
would derive the two other ones: CRectangle and CTriangle.
The class CPolygon would contain members that are common for both types of polygon.
In our case: width and height. And CRectangle and CTriangle would be its derived
classes, with specific features that are different from one type of polygon to the other.
Classes that are derived from others inherit all the accessible members of the base class.
That means that if a base class includes a member A and we derive it to another class with
another member called B, the derived class will contain both members A and B.
In order to derive a class from another, we use a colon (:) in the declaration of the
derived class using the following format:
Exception Handling
Furthermore, exception handling in C++ propagates the exceptions up the stack; therefore,
if there are several functions called, but only one function that needs to reliably deal with errors,
the method C++ uses to handle exceptions means that it can easily handle those exceptions
without any code in the intermediate functions. One consequence is that functions don't need
to return error codes, freeing their return values for program logic.
When errors occur, the function generating the error can 'throw' an exception. For example,
take a sample function that does division:
const int DivideByZero = 10;
//....
double divide(double x, double y)
{
if(y==0)
{
throw DivideByZero;
}
return x/y;
}
The function will throw DivideByZero as an exception that can then be caught by an exception-
handling catch statement that catches exceptions of type int. The necessary construction for
catching exceptions is a try catch system. If you wish to have your program check for
exceptions, you must enclose the code that may have exceptions thrown in a try block. For
example:
try
{
divide(10, 0);
}
catch(int i)
{
if(i==DivideByZero)
{
cerr<<"Divide by zero error";
}
}
The catch statement catches exceptions that are of the proper type. You can, for example,
throw objects of a class to differentiate between several different exceptions. As well, once a
catch statement is executed, the program continues to run from the end of the catch.
It is often more useful for you to create a class that stores information on exceptions as they
occur. For example, it would be more useful if you had a class to handle exceptions.
class DivideByZero
{
public:
double divisor;
DivideByZero(double x);
};
DivideByZero::DivideByZero(double x) : divisor(x)
{}
int divide(int x, int y)
{
if(y==0)
{
throw DivideByZero(x);
}
}
try
{
divide(12, 0);
}
catch (DivideByZero divZero)
{
cerr<<"Attempted to divide "<<divZero.divisor<<" by zero";
}
If you wish to catch more than one possible exception, you can specify separate catch blocks for
each type of exception. It's also possible to have a general exception handler that will respond
to any thrown exception. To use it, simply use catch(...) for the catch statement and print a
general warning of some kind.
The handy thing to remember about exception handling is that the errors can be handled
outside of the regular code. This means that it is easier to structure the program code, and it
makes dealing with errors more centralized. Finally, because the exception is passed back up
the stack of calling functions, you can handle errors at any place you choose.
In C, you might see some error handling code to free memory and close files repeated five or or
six times, once for each possible error. A solution some programmers prefered was to use a
goto statement that jumped all the way to the cleanup code. Now, you can just surround your
code with a try-catch block and handle any cleanup following the catch (possibly with an
additional copy of your cleanup routine inside the catch block if you intend to throw the
exception again to alert the calling function of an error).
try {
// code that could throw an exception
}
[ catch (exception-declaration) {
// code that executes when exception-declaration is thrown
// in the try block
}
[catch (exception-declaration) {
// code that handles another exception type
} ] . . . ]
// The following syntax shows a throw expression:
throw [expression]
Remarks
The C++ language provides built-in support for handling anomalous situations, known as exceptions,
which may occur during the execution of your program. The try, throw, and catch statements
implement exception handling. With C++ exception handling, your program can communicate
unexpected events to a higher execution context that is better able to recover from such abnormal
events. These exceptions are handled by code that is outside the normal flow of control. The Microsoft
C++ compiler implements the C++ exception handling model based on the ANSI C++ standard.
C++ also provides a way to explicitly specify whether a function can throw exceptions. You can use
exception specifications in function declarations to indicate that a function can throw an exception. For
example, an exception specification throw(...) tells the compiler that a function can throw an
exception, but doesn't specify the type, as in this example:
Copy Code
void MyFunc() throw(...) {
throw 1;
}
Note:
The Win32 structured exception-handling mechanism works with both C and C++ source files.
However, it is not specifically designed for C++. You can ensure that your code is more portable by
using C++ exception handling. Also, C++ exception handling is more flexible, in that it can handle
exceptions of any type. For C++ programs, it is recommended that you use the C++ exception-
handling mechanism (try, catch, throw) described in this topic.
The code after the try clause is the guarded section of code. The throw expression throws (raises) an
exception. The code block after the catch clause is the exception handler, and catches (handles) the
exception thrown by the throw expression. The exception-declaration statement indicates the type of
exception the clause handles. The type can be any valid data type, including a C++ class. If the
exception-declaration statement is an ellipsis (...), the catch clause handles any type of exception,
including C exceptions and system- or application-generated exceptions such as memory protection,
divide by zero, and floating-point violations. Such a handler must be the last handler for its try block.
1. Control reaches the try statement by normal sequential execution. The guarded section
follow the try block are not executed. Execution continues at the statement after the last
catch clause following the associated try block.
3. If an exception is thrown during execution of the guarded section or in any routine the
guarded section calls (either directly or indirectly), an exception object is created from the
object created by the throw operand. (This implies that a copy constructor may be
involved.) At this point, the compiler looks for a catch clause in a higher execution context
that can handle an exception of the type thrown (or a catch handler that can handle any
type of exception). The catch handlers are examined in order of their appearance following
the try block. If no appropriate handler is found, the next dynamically enclosing try block is
examined. This process continues until the outermost enclosing try block is examined.
4. If a matching handler is still not found, or if an exception occurs while unwinding, but before
the handler gets control, the predefined run-time function terminate is called. If an
exception occurs after throwing the exception, but before the unwind begins, terminate is
called.
5. If a matching catch handler is found, and it catches by value, its formal parameter is
The following is a simple example of a try block and its associated catch handler. This example
detects failure of a memory allocation operation using the new operator. If new is successful, the
catch handler is never executed:
Copy Code
// exceptions_trycatchandthrowstatements.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
int main() {
char *buf;
try {
buf = new char[512];
if( buf == 0 )
throw "Memory allocation failure!";
}
catch( char * str ) {
cout << "Exception raised: " << str << '\n';
}
}
The operand of the throw expression specifies that an exception of type char * is being thrown. It is
handled by a catch handler that expresses the ability to catch an exception of type char *. In the
event of a memory allocation failure, this is the output from the preceding example:
Copy Code
Exception raised: Memory allocation failure!
The real power of C++ exception handling lies not only in its ability to deal with exceptions of varying
types, but also in its ability to automatically call destructor functions during stack unwinding, for all
local objects constructed before the exception was thrown.
The following example demonstrates C++ exception handling using classes with destructor semantics:
Example
Copy Code
// exceptions_trycatchandthrowstatements2.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
void MyFunc( void );
class CTest {
public:
CTest() {};
~CTest() {};
const char *ShowReason() const {
return "Exception in CTest class.";
}
};
class CDtorDemo {
public:
CDtorDemo();
~CDtorDemo();
};
CDtorDemo::CDtorDemo() {
cout << "Constructing CDtorDemo.\n";
}
CDtorDemo::~CDtorDemo() {
cout << "Destructing CDtorDemo.\n";
}
void MyFunc() {
CDtorDemo D;
cout<< "In MyFunc(). Throwing CTest exception.\n";
throw CTest();
}
int main() {
cout << "In main.\n";
try {
cout << "In try block, calling MyFunc().\n";
MyFunc();
}
catch( CTest E ) {
cout << "In catch handler.\n";
cout << "Caught CTest exception type: ";
cout << E.ShowReason() << "\n";
}
catch( char *str ) {
cout << "Caught some other exception: " << str << "\n";
}
cout << "Back in main. Execution resumes here.\n";
}
Copy Code
In main.
In try block, calling MyFunc().
Constructing CDtorDemo.
In MyFunc(). Throwing CTest exception.
Destructing CDtorDemo.
In catch handler.
Caught CTest exception type: Exception in CTest class.
Back in main. Execution resumes here.
Comments
Note that in this example, the exception parameter (the argument to the catch clause) is declared in
both catch handlers:
Copy Code
catch( CTest E )
// ...
catch( char *str )
// ...
You do not need to declare this parameter; in many cases it may be sufficient to notify the handler
that a particular type of exception has occurred. However, if you do not declare an exception object in
the exception-declaration, you will not have access to that object in the catch handler clause.
A throw-expression with no operand re-throws the exception currently being handled. Such an
expression should appear only in a catch handler or in a function called from within a catch handler.
The re-thrown exception object is the original exception object (not a copy). For example:
Copy Code
try {
throw CSomeOtherException();
}
catch(...) { // Handle all exceptions
// Respond (perhaps only partially) to exception
throw; // Pass exception to some other handler
}
Exception Specifications
Exception specifications are used to provide summary information about what exceptions can be
thrown out of a function. For example:
Copy Code
void MyFunction(int i) throw(...);
Copy Code
void MyFunction(int i) throw();
tells the compiler that the function does not throw any exceptions. It is the equivalent to using
__declspec(nothrow). For example:
Copy Code
// exceptions_trycatchandthrowstatements3.cpp
// compile with: /EHsc
#include <stdio.h>
int main() {
try {
empty();
with_type();
}
catch (int) {
puts("Caught an int");
}
}
Visual C++ departs from the ANSI Standard in its implementation of exception specifications. The
following table summarizes the Visual C++ implementation of exception specifications:
Exception
specification Meaning
throw() The function does not throw an exception. However, if an exception is thrown out
of a function marked throw(), the Visual C++ compiler will not call unexpected
(see unexpected (CRT) and unexpected (<exception>) for more information). If a
function is marked with throw(), the Visual C++ compiler will assume that the
function does not throw C++ exceptions and generated code accordingly. Due to
code optimizations that maybe performed by the C++ compiler (based on the
assumption that the function does not throw any C++ exceptions) if function does
throw an exception, the program may not execute correctly.
throw(type) The function can throw an exception of type type. However, in Visual C++ .NET,
this is interpreted as throw(...). See Function Exception Specifiers.
If exception handling is used in an application, there must be one or more functions that handle
thrown exceptions. Any functions called between the one that throws an exception and the one that
handles the exception must be capable of throwing the exception.
Copy Code
// exception_specification.cpp
// compile with: /EHs
#include <stdio.h>
void handler() {
printf_s("in handler\n");
}
int main() {
f2();
try {
f4();
}
catch(...) {
printf_s("Caught exception from f4\n");
}
f5();
}
Copy Code
About to throw 1
in handler
About to throw 1
Caught exception from f4
About to throw 1
in handler
Function-try Blocks
A function-try block catches exceptions generated from a call made in a constructor's initialization list.
Example
Visual C++
Copy Code
// NVC_Function_Try.cpp
// Compile by using /EHsc
#include <iostream>
#include <ostream>
#include <stdexcept>
#include <string>
using namespace std;
class Name
{
public:
Name(const string& strFirstName, const string& strLastName)
{
if(strFirstName == "")
{
throw runtime_error("Error in Name::Name(): first name
parameter must not be blank.");
}
if(strLastName == "")
{
throw runtime_error("Error in Name::Name(): last name
parameter must not be blank.");
}
m_firstName = strFirstName;
m_lastName = strLastName;
string GetName()
{
return m_firstName + " " + m_lastName;
}
private:
string m_firstName;
string m_lastName;
};
class Person
{
public:
Person(const string& strFirstName, const string& strLastName)
// begin function-try block
try : m_name(strFirstName, strLastName){}
catch (const runtime_error& err)
{
cout << "Caught exception inside Person::Person()." << endl;
cout << err.what() << endl;
// The exception will be automatically rethrown.
}
~Person()
{
cout << "Inside Person::~Person(): Destroying " <<
m_name.GetName() << "." << endl;
}
private:
Name m_name;
};
int main()
{
try
{
// Test 1 - Valid Parameters
{
cout << "Attempting to construct an object of class
Person." << endl;
cout << "Passing parameters: \"John\", \"Smith\"" <<
endl;
Person person1("John", "Smith");
}
// Output:
// --------------------------
// Attempting to construct an object of class Person.
// Passing parameters: "John", "Smith"
// Inside Name::Name(): My name is John Smith.
// Inside Person::~Person(): Destroying John Smith.
// Inside Name::~Name(): Destroying John Smith.
// Test 1 completed.
// Attempting to construct an object of class Person.
// Passing parameters: "", "Jones"
// Caught exception inside Person::Person().
// Error in Name::Name(): first name
// Exception caught within main().
With the new synchronous exception model, now the default, exceptions can be thrown only with a
throw statement. Therefore, the compiler can assume that exceptions happen only at a throw
statement or at a function call. This model allows the compiler to eliminate the mechanics of tracking
the lifetime of certain unwindable objects, and to significantly reduce the code size, if the objects'
lifetimes do not overlap a function call or a throw statement. The two exception handling models,
synchronous and asynchronous, are fully compatible and can be mixed in the same application.