Vous êtes sur la page 1sur 30

C++ Notes

Compiled by:
Watsh Rajneesh
Software Engineer @ Quark (R&D Labs)
4/4/2002
wrajneesh@bigfoot.com
References:
The following notes has been compiled by the author referring to several sources, most important ones
being:
1.Stanley B. Lippman's C++ Primer (3rd Edition) and
2.Effective C++, Second Edition: 50 Specific Ways to Improve Your Programs and Designs, Scott Meyers,
Addison-Wesley, 1998, ISBN 0-201-92488-9.
3.More Effective C++, Second Edition, Scott Meyers, Addison-Wesley, 1998.
4.NCST's handouts for OOPS programming using C++.(Issue: 1998-99) (Visit them at http://ncst.ernet.in)
5.MSDN Visual C++ Programmer's Guide.
PART I -- Basic C++
This section is my notes from Lippman's book (which i adore) plus the NCST handouts and the MSDN. It
has basic C++ features captured as they appeared in the book by Lippman. If you feel you are thorough
with the basics then you may start reading the next section which has tips for matured C++ programmers
who already know the language semantics very well. This notes on the basics will be most useful for those
who only need to revise their C++ knowledge. The notes below is not a tutorial on the language and so will
help only those readers who have read and understood at least one basic book on C++.

1. The volatile keyword is a type qualifier used to declare that an object can be modified in the program by
something other than statements, such as the operating system, the hardware, or a concurrently executing
thread.

The following example declares a volatile integer nVint whose value can be modified by external
processes:

int volatile nVint;


Objects declared as volatile are not used in optimizations because their value can change at any time. The
system always reads the current value of a volatile object at the point it is requested, even if the previous
instruction asked for a value from the same object. Also, the value of the object is written immediately on
assignment.

One use of the volatile qualifier is to provide access to memory locations used by asynchronous processes
such as interrupt handlers.

2. Nested Class
A class that is declared within the scope of another class. A nested class is available for use within the scope
of the enclosing class. To refer to a nested class from a scope other than its immediate enclosing scope, a
fully qualified name must be used.Eg: A linked stack,
template <class T>
class Stack {
public:
Stack();
~Stack();

void push(const T& object);


T pop();

bool empty() const; // is stack empty?


private:
struct StackNode { // linked list node
T data; // data at this node
StackNode *next; // next node in list

// StackNode constructor initializes both fields


StackNode(const T& newData, StackNode *nextNode)
: data(newData), next(nextNode) {}
};

StackNode *top; // top of stack

Stack(const Stack& rhs); // prevent copying and


Stack& operator=(const Stack& rhs); // assignment
};

Stack::Stack(): top(0) {} // initialize top to null

template <class T>


void Stack::push(const T& object)
{
top = new StackNode(object, top); // put new node at
} // front of list

template <class T>


T Stack::pop()
{
StackNode *topOfStack = top; // remember top node
top = top->next;

T data = topOfStack->data; // remember node data


delete topOfStack;

return data;
}

Stack::~Stack() // delete all in stack


{
while (top) {
StackNode *toDie = top; // get ptr to top node
top = top->next; // move to next node
delete toDie; // delete former top node
}
}

bool Stack::empty() const


{ return top == 0; }
Note: That's the hallmark of a template class: the behavior doesn't depend on the type. Another simpler
example is that of a parametrized linked list class. This source is compiled in MSVC++6.0.

LinkedList.h listing:

#include <iostream>
#include <iomanip>
using namespace std;

#include <iostream>
#include <iomanip>
using namespace std;
template <class T>
class LinkedList {
private:
struct Node {
T _data;
struct Node* _next;
Node(T data):_data(data),_next(NULL) {}
};
unsigned int _count;
Node* _head;
public:
LinkedList()
{
_count = 0;
_head = NULL;
}

~LinkedList()
{
Node* p;
// release the allocated memory from the list.
for (p = _head;p != NULL;) {
Node* temp = p;
p = p->_next;
delete temp;
}
}
void put(T data);
void print();
void deleteNode(T data);
T getNodeInfo(int index);
};

LinkedList.cpp listing:

#include "LinkedList.h"

template <class T>


void LinkedList<T>::print() {
if(_head == NULL) {
cout << "Empty Linked List" << endl;
}
Node* p;
int i;
for (p = _head,i = 1 ;p != NULL;p = p->_next,i++) {
cout << i << "th node of " << _count << " nodes: " << p->_data << endl;
}
}

template <class T>


void LinkedList<T>::put(T data) {
if(_head == NULL) {
// add to the head of the list.
_head = new Node(data);
_count++;
return;
}
Node* p;
for (p = _head;p->_next != NULL;p = p->_next);
// add the new node to the end of the list.
p->_next = new Node(data);
_count++;
}

template <class T>


void LinkedList<T>::deleteNode(T data)
{
if (_head == NULL)
return;
if(_head->_data == data) {
Node *temp;

temp = _head;
_head = _head->_next;
delete temp;
return;
}
Node *p;
for(p = _head;p->_next != NULL; p=p->_next) {
if(p->_next->_data == data) {
Node *temp;

temp = p->_next;
p->_next = p->_next->_next;
delete temp;
break;
}
}
}

template <class T>


T LinkedList<T>::getNodeInfo(int index)
{
if(_head == NULL) return NULL;
if(index < 0) return NULL;
if(index == 0) {
return _head->_data;
}
Node *p;
int i;
for(i = 0,p = _head;(i <= index); i++) {
if (p == NULL) {
cout<<"Cannot find the node: List too small"<<endl;
break;
}
p = p->_next;
}
if (p != NULL) {
cout<<"Node info at "<< index <<"is " << p->_data;
}
return p->_data;
}

int main() {
LinkedList<int> list;

list.put(2);
list.put(3);
list.put(4);
list.print();

LinkedList<char> list2;

list2.put('A');
list2.put('B');
list2.put('C');
list2.print();
list2.deleteNode('A');
list2.print();
char data0 = list2.getNodeInfo(0);
cout << "data0 : " << data0<<endl;
return 0;
}
3. Shallow vs. Deep Copies:
A shallow copy of an object copies all of the member field values. This works well if the fields are values,
but may not be what you want for fields that point to dynamically allocated memory. The pointer will be
copied. but the memory it points to will not be copied -- the field in both the original object and the copy
will then point to the same dynamically allocated memory, which is not usually what you want. The default
copy constructor and assignment operator make shallow copies.

A deep copy copies all fields, and makes copies of dynamically allocated memory pointed to by the fields.
To make a deep copy, you must write a copy constructor and overload the assignment operator. If the object
has no pointers to dynamically allocated memory, a shallow copy is probably sufficient. Therefore the
default copy constructor, default assignment operator, and default destructor are ok and you don't need to
write your own. Copies of an object are made when
A declaration is made with initialization from another object, eg,
Person q("Mickey"); // constructor.
Person p = q; // copy constructor.
Person r(p); // copy constructor.

1. Parameters are passed by value.


2. An object is returned by a function.
3. C++ calls a copy constructor to make the copy. If there is no copy constructor defined for the
class, C++ uses the default copy constructor which copies each field, ie, a shallow copy.

A destructor for an object is called whenever :

1. The scope of a local variable or parameter is exited, the destructor is called.


2. By explicitly calling the destructor.

If your object has a pointer to memory that was dynamically allocated previously, eg in the constructor, you
will want to make a deep copy of the object. Deep copies require overloading assignment, as well as
defining a copy constructor and a destructor). The following steps are a typical for the assignment operator.

Test to make sure you aren't assigning an object to itself, eg x = x. Of course no one would write that
statement, but in can happen when one side of the assignment is not so obviously the same object. Because
the memory associated with the object is going to be freed, you don't want to free it and then try to use it.

1. Delete the associated memory. Same as copy constructor.


2. Copy all the members. Same as copy constructor.
3. Return *this. This is necessary to allow multiple assignment, eg x = y = z;
Person& Person::operator=(const Person& p) {
if (this != &p) { // make sure not same object
delete [] _name; // free old name's memory.
_name = new char[strlen(p._name)+1]; // get new space
strcpy(_name, p._name); // copy new name
_id = p._id; // copy id
}
return *this; // return ref for multiple assignment
}//end operator=

4. The manipulator "endl" inserts a newline character into the output stream and then flushes the o/p buffer.
Its a part of <iomanip.h>.

5. The initialization of a derieved class object is carried out by automatic invocation of each base class
constructor to initialize the associated base class subobject followed by execution of the derived class
constructor. From a design standpoint, the constructor for derieved class should initialize only those data
members defined within the derived class itself and not those of its base class.

6. The base class constructors, destructor and copy assignment operators are not inherited by derived class
and so must be provided in the derived class. The syntax of derived class constructor provides the interface
for passing arguments to the base class constructor.
eg: inline IntArrayRC::IntArrayRC(int sz):IntArray(sz) {}
inline IntArrayRC::IntArrayRC(const int*iar,int
sz):IntArray(iar,sz) {}
Thus, bodies of IntArrayRC constructors is null, as it does not have any of its own data members to
initialize. The ":" portion of constructor called "member initialization list" provides a mechanism by which
IntArray constructor is passed its arguments.

7. Two additional forms of inheritance:


a) Multiple Inheritance -- a class is derived from two or more base classes.
b) Virtual Inheritance -- a single instance of a base class is shared among multiple derived classes.

8. The ability to query a base class reference or pointer as to the actual type it refers to at runtime is
provided by Runtime Type Identification.

9. Virtual functions are less efficient than non-virtual functions as they are resolved at runtime causing
overhead in identifying which one of them is to be invoked.

10. C++ template facility provides for "parametrizing" types and values used within a class or function
definition. These parameters serve as placeholders in otherwise invariant code; later parameters are bound
to actual types, either built-in or user defined types.
eg:
template <class elemType>
class Array {
public:
explicit Array (int size = DefaultArraySize);
Array(elemType* array,int arraySize);
Array(const Array&);
virtual ~Array() { delete[] ia;}
bool operator== (const Array&)const;
bool operator!= (const Array&)const;
Array& operator= (const Array&);
int size() const { return _size;}
virtual elemType& operator[] (int index) { return ia[index];}
virtual void sort();
virtual elemType min() const;
virtual elemType max() const;
virtual int find(const elemType& value) const;
protected:
static const int DefaultArraySize=12;
int _size;
elemType* ia;
};

11. When an exception is raised, normal program execution is suspended until the exception is handled.
Raising an exception is carried out by a throw expression. Program exceptions are raised and handled in
separate functions or member-function invocations. A try block groups one or more program statements
with one or more catch clauses. Code that can throw an exception is placed in try block. When exception is
thrown, the corresponding catch block is entered, where the exception is supposed to be handled.
Parenthesis of catch block contains argument(s). Naming the argument is optional. Throwing and catching
involves search for a matching catch block handler. If an appropriate handler is not found the program will
be terminated. Catch handlers are tried in order of their occurance after the try block. Handler can rethrow
an exception (throw is used without any argument). Exceptions can be nested. An exception is an object
instance. Adding multiple catch blocks for a try block can handle multiple exceptions. Tip: Avoid dynamic
allocations inside a try block. Exceptions can be grouped using:
a) enumerations
b) inheritance -- this is the prefered way.
class NetworkError {};
class HostNotFound:public NetworkError {};
class BadPort:public NetworkError {};

try {
Socket s;
s.connect("konark.ncst.ernet.in",9999);
string buf = s.readInput();
}
catch(HostNotFound) {//...}
catch(BadPort) {//...}
catch(NetworkError) {//...}

The ordering of catch handlers is important as exceptions can be caught by a handler using base class rather
than the exact class. So, if catch(NetworkError) appeared first then the other two catch blocks will never
have caught an exception.

12. The "namespace" mechanism allows to encapsulate names that otherwise pollute the global
namespace. We use namespaces only when we expect our code to be used in external software sites.Eg:
namespace my_namespace {
template <class elemType>
class Array {//...};
//...
};

The declaration within a namespace are made visible to a program as, namespace_identifier::entity_name;
Eg: my_namespace::Array<string> text;

The alias facility of namespace can be used as,


namespace mns = my_namespace;
so that,
mns::Array<string> text;

The using directive makes the declarations within a namespace visible so that they can be referred to
without qualification. Eg:
using namespace my_namespace;

All components of the C++ standard library are declared within a namespace called std. To make this
visible:
using namespace std;

or #include <string.h>
using std::string;
Ideally, a code should have a using declaration for each library component it uses.
namespace Exercise {
template <class elemType>
class Array {...};

template <class eType>


void print(Array<eType>*);

class String {...};

template <class listType>


class List {...};
};

using namespace Exerice;


int main() {
const int size = 1024;

Array<String> as(size);
List<int> il(size);
//...
Array<String> *pas = new Array<String> (as);
List<int> *pil = new List<int> (il);
print(*pas);
}

12. Vector:
a) can grow dynamically at runtime
b) Vector class provides a minimal set of operations like relational operations, size() and empty(). The
general operations such as sort(), min(), max(), find(), and so on are instead provided as independent set of
generic algorithms.
#include<vector.h>
vector<int> vec1; // empty vector
vector<int> vec2(size,value); //each element=value
int ia[4]={0,1,2,3};
vector<int> vec3(ia,ia+4); // elements = 0,1,2,3
vector<int> vec4(size); //elements = 0
vector<int> vec5(vec2); // copy

An iterator is a class object supporting the abstraction of a pointer type. The vector class template provides
a begin() and end() pair of operations returning an iterator to respectively the beginning of vector and 1 past
the end of the vector.
vector<int> vec(size);
vector<int>::iterator iter = vec.begin();
for(int i = 0;iter != vec.end();++iter,++i)
*iter = i;
iterator is a typedef defined inside the vector class template holding elements of type int.

13. To use generic algorithms we must include <algorithm.h>. Standard library also provides support for a
map associative array -- elements of which can be indexed by something other than integer values. Eg:
template <class elemType>
elemType min(const vector<elemType> &vec) {
vector<elemType>::iterator iter = vec.begin();
elemType min = *iter;
++iter;
for(;iter != vec.end();++iter)
min = (min < *iter)? min : *iter;
return min;
}

14. sizeof operator returns a value of type size_t a machine specific typedef found in <cstddef.h>. sizeof()
is considered a constant expression.
Eg: int array[sizeof(some_type_T)];

15. Four types of casts:


a) static_cast -- Any type of conversion which the compiler implicitly performs can be made explicit
through the use of a static_cast. Eg:
double d = 97.0;
char ch = static_cast<char>(d);
b) dynamic_cast --
c) const_cast -- casts away the const-ness of its expression.
extern char* string_copy(char*);
const char* pc_str;
char* pc = string_copy(const_cast<char*>(pc_str));
d) reinterprest_cast -- performs a low-level implementation of bit pattern of its operands.As its name
suggests, this style of cast reinterprets its operand's representation as having the target type. This
reinterpretation involves no calls to conversion constructors or conversion operators; indeed, a
reinterpret_cast may leave the operand's bit pattern intact, so that the conversion is purely a compile-time
act with no run-time consequences.

16.A goto statement may not jump over a declaration statement that is not enclosed within a statement
block.
int oops_in_error() {
//...
goto end; //error
int ix = 10;
// ...
end: ...;
}
We can resolve this by placing declaration statement and any statements making use of that declaration
within a statement block. A backward jump over an initialized object definition is legal, though.

17. Saints words on C++:


a) Never forget to initialize the non-static const and the reference objects.
b) Initialize all data members using constructors.
c) Always write support for assignment operator whenever there exists a copy constructor.

18. Functions:
18.1 Under standard C++, the return type for a function cannot be omitted. In the absence of explicit
return type it is not assumed to be of return type int.
18.2 The parameter list of function is refered to as the signature of the function.
18.3 C++ is a strongly typed language. The declaration of a function is necessary for compiler to perform
type checking on arguments of function call against the function parameter list. Omitting an argument or
passing an argument of wrong type is caught due to strong type-checking in C++, at compile time.
18.4 Functions use allocated storage on runtime stack. That remains associated with the function until
function terminates. At that point, storage is automatically made available for reuse. Entire storage area of
function is referred to as "activation record". Each function parameter is provided storage within it.
18.5 Functions within a program preferrably should communicate information through the use of their
parameter lists and return values. Eight parameters should be maximum. As an alternative to a large
parameter list try declaring a parameter to be a class, an array or one of container types; such a parameter
can be used to contain a group of parameter values.
18.6 Unlike a non-inline function, an inline function must be defined in every text file in which inline
function is called. Ofcourse, the definitions of inline function that appear in different files that compose a
program must be same. So, it is recommended to place the definition of inline function in a header file and
to include the header file in every text file in which the inline function is called.
18.7 Pointer to functions: A function name is not a part of its type. A functions type is determined only
by its return type and its parameter list. A pointer to a function lexicocompare() is:
int lexicocompare(const string& s1,const string& s2);
int (*pf) (const string& s1, const string& s2);
Another function:
int sizecompare(const string& s1, const string& s2);
can also be pointed at by pf as it too has the same type. A function name when not modified by call
operator, evaluates as a pointer to a function of its type i.e.
lexicocompare;
evaluates to
int (*) (const string& s1,const string& s2);
Applying an address-of operator(&) to function name also yields a pointer to a function of its function type.

int (*pif2) (const string&,const string&) = &lexicocompare;


We can also assign
pif2 = pf;
or
pif2 = NULL;
Both a direct call to a function using function name and an indirect call to a function using a pointer can be
written the same way (no dereferencing is required.).
Eg:
int min(int*, int);
int (*pf)(int*,int) = min;
int main() {
const int iasize = 5;
int ia[iasize] = {1,2,3,4,5};
min(ia,iasize); //direct call
pf(ia,iasize); //indirect call
return 0;
}
Another example: int (*testcases[10])();
testcases is an array of 10 elements. Each element is a pointer to a function that takes no arguments and that
has a return type of int.
Alternatively,
typedef int (*PVF) ();
PVF testcases[10];
19. References:
19.1 References are used to pass large class objects to a function as time and space costs to allocate and
copy the class object in pass-by-value on to the stack are often too high for real world applications.
19.2 Whenever a reference parameter is not intended to be modified within the function called, it is a
good practice to declare the parameter as a reference to a const type.
19.3 Reference Vs Pointers: A reference must be initialized to an object and once initialized, it can never
be made to refer to another object. A pointer can address a sequence of different objects or no object at all.
So, with a reference parameter, function doesnot need to guard against its referring to no object. If a
parameter may refer to different objects within a function or if the parameter may refer to no object at all, a
pointer parameter must be used. One important use of reference parameters is to allow us implement
overloaded operators efficiently while keeping their use intuitive. So pass-by-value causes overhead and
use of pointers decreases the intuitiveness of operator overloading.
19.4 Reference to array: If parameter is a reference to an array, the array size is a must as compiler
checks that size of array argument matches one specified in function parameter type. Eg:
void putval(int(&arr)[10]);
int main() {
int j[2];
putval(j); //error: sizes not same
}

20. Ellipses suspend typechecking. Their presence tells the compiler that when function is called, zero or
more arguments may also follow and that types of arguments are unknown.
void foo(...);
int printf(const char*,...); // Note: the use of , is optional before
ellipses.
Most functions (like printf()) with an ellipses use some information from a parameter that is declared to
obtain type and number of optional arguments provided in a function call.

21. If return value is a large class object, using a reference (or a pointer) return type is more efficient.

22. Caution: While returning an lvalue. To prevent unintended modification of a reference return value, the
return value should be declared as const. Eg:
const int& getValue(vector<int>&vi,int ix);
Donot use references when returning a local object declared within the function, use a non-reference return
type.

23. Scope:
23.1 local, namespace,class. Outermost scope of a program is called global scope. Programmer can
define user defined namespaces nested within global scope using namespace definitions.
23.2 The declarations of a global object that specifies both extern keyword and an explicit initializer is
treated as a definition of that object. Otherwise, extern only declares an object without defining it
promising that the object is defined elsewhere, either somewhere in the test file or in another text file of the
program.
Eg: extern const double pi = 3.14;
extern int obj1;

24. It is recommended that object created with new expression be initialized.


Eg: int * pi = new int(0);
In case of class objects the value(s) in the parenthesis are passed to associated constructor of the class,
which is invoked following the successful allocation of object. If the operator new() called by the new
expression cannot acquire the requested memory, in general, it throws an exception called bad_alloc.

25. The language guarantees that operator delete() is not called by delete expression if the pointer operand
is set to 0. It is a good idea to set the pointer to NULL after the object it refers to has been deleted to avoid
dangling pointer problem. Fiailing to apply delete expression causes memory leak.

26. A programmer may want to create the object on the free store but prevent program from changing the
value of the object once it has been initialized.
const int* pci = new const int(10);
//...
delete pci;

27. Overloaded functions:


a) If parameter lists of two functions match exactly but return types differ, then second definition is
treated as erroneous redeclaration of first and is flagged as compile time error.
unsigned int max(int, int);
int max(int, int); //redeclaration
b) If parameter lists of two functions differ only in their default arguments, the second declaration is
treated as a redeclaration of the first.
int max(int* ia, int size);
int max(int*, int = 10); // redeclaration.
c) The const or volatile qualifiers are not taken into account.
void f(int);
void f(const int); //redeclaration
But if const or volatile qualifiers appy to type which is a pointer or reference parameter, then the const or
volatile qualifier is taken into account when declaration of different functions are identified.
void f(int*);
void f(const int*); //overloaded

void f(int&);
void f(const int&); //overloaded

28. A user cannot specify a parameter list in using declaration for a function:
using libs_R_US::print;
A using declaration always declares aliases for all the functions in a set of overloaded functions. The
functions introduced by using declaration overload the other declarations of the functions with the same
name already present in the scope where the using declaration appears. If it introduces a function that
already has its exact match in the scope, then a compile time error occurs.

29. template parameter list consists of a comma separated list of parameters. It cannot be empty. Template
parameters can be,
i> template type parameters or
ii> template non-type parameters representing a constant expression.
Keyword for template type parameter is "class" or "typename" followed by an identifier. These keywords
have some meaning for function templates. They indicate that parameter name that follows represent a
potential built-in or user-defined type.
A template non-type parameter is an ordinary parameter declaration. It indicates that parameter name
represents a potential value which represents a constant in template definition.
Eg:
template <class Type, int size>
Type min(const Type(&r_array)[size]) {
Type min_val = r_array[0];
for(int i = 1;i < size;i++){
if(r_array[i]<min_val)
min_val = r_array[i];
}
return min_val;
}

int main() {
int ia[5] = {1,2,3,4,5};
int i = min(ia);
}
Passing array as a reference alleviates the problem of passing a second argument indicating its size. A
template non-type parameter serves as a constant value and can be used when constant values are required,
prehaps to specify the size of array or an initial value of an enum constant. Any object or function or type in
global scope having same name as a template parameter is hidden.
template <class T1,class T2,class T3> //ok
T1 min(T2,T3);

The name of the template parameter can be used only once with same template type.
Eg: template <class Type,class Type> // error
To declare a function template as inline:
template<class Type>
inline Type min(Type, Type);

A function template is instantiated either when it is invoked or when its address is taken.
template <typename Type,int size>
Type min(Type (&r_array)[size]) { }

int(*pf)(int(&)[10]) = &min;
int i = pf(ia);

30. An exception is an object, and throw must throw an object of class type.
throw popOnEmpty();
A throw expression can throw an object of any type:
enum EHState {noErr, zeroOp};
if(i == 0) throw zeroOp;
The try block groups a set of statements and associates with these statements a set of catch handlers to
handle the exceptions that the statement can throw.
Eg:
//StackExcep.hpp
class PopOnEmpty {}
class PushOnFull {}

//Stack.cpp
//in main...
try {
for(int i = 1; i < 5;++i) {
if(i % 3 == 0)
Stack.push(i);
if(i%4 == 0)
Stack.display();
if(i % 10 == 0) {
int dummy;
Stack.pop(dummy);
Stack.display();
}
}
}
catch(PushOnFull){}
catch(PopOnEmpty) {}
If pop() throws an exception, the call to display() is ignored, try block is exited and handler for exception of
type popOnEmpty is executed. Each catch clause specifies within parenthesis the type of exception it
handles. If no catch clause capable of handling the exception exists, program execution resumes in function
terminate defined in C++ standard library. The default behaviour of terminate is to call abort() indicating
abnormal exit from program.
try block introduces a local scope and variables declared within a try block cannot be referred to outside the
try block, including within catch clauses.
Instead of placing try block within the function definition we can enclose the function body within a
"function try block".
Eg:
int main
try { /* start of main */
//...
} /* end of main */
catch(...) {}
A function try block associates a group of catch clauses with a function body. This is particularly useful
within class constructors.
A catch clause can take the declaration of a single type or single object within parenthesis. If the catch
clause doesnot contain a return statement then program execution continues after catch clause has
completed its work, from the statement that follows the last catch clause in the list. Thus the exception
handling is non-resumptive.
In catch clause the exception declaration can be either a type declaration or an object declaration. An object
is declared when it is necessary to obtain the value or manipulate the exception object created by the throw
expression.
Eg:
void Stack::push(int value) {
if(full) throw PushOnFull(value); // value stored in the exception
object.
//...
}
class PushOnFull {
public:
PushOnFull(int val):_value(val){}
int value() {return _value;}
private:
int _value;
}
catch(PushOnFull eObj) {
cerr<<"trying to push "<<eObj.value()<<" on a full stack!";
}
An exception object is always created at throw point. When a catch clause is entered, if the exception
declaration declares an object, this object is initialized with a copy of exception object thrown.
Alternatively, the catch clause object can directly refer to the exception object created by the throw
expression instead of creating a local copy by using reference declaration.
enum EHState {noErr, zeroOp};
enum EHState state = noErr;
void calculate(int op) {
try {
mathFunc(op);
}catch(EHState &eObj) {
}
}
int mathFunc(int i) {
if(i == 0) {
state = zeroOp;
throw state; // exception object created.
}
}
This is more efficient that exception declarations for exceptions of class type be declared as references.
Note: With an exception declaration of reference type, the catch clause is able to modify the exception
object. However, any variable specified by throw expression remains unaffected. C++ exception handling
requires runtime support.
A catch clause can pass the exception to another catch clause further up the list of fucntion calls by
rethrowing the exception.
throw;
It rethrows the exception object. The exception rethrown is the original exception object. This has some
implications if the catch clause modifies the exception object before rethrowing it. To modify the original
exception object, the exception declaration within catch clause must declare a reference.
Eg:
catch(EHState &eObj) {
catch(EHState eObj) {
//...
//...
eObj = negativeOp;
eObj = negativeOp;
throw; // exception
throw; // original
rethrown has value
exception object rethrown
// negativeOp
}
}
This is a good reason to declare the exception declaration of catch clause as reference to ensure that
modifications applied to exception object within the catch clause are reflected in the exception object
rethrown.
A function may acquire some resource (file or memory on a heap) and may want to release this resource
(close the file or release the memory to heap) before it exits when an exception is thrown. Eg:
void mainp() {
resource res;
res.lock();
try {
// exception may be thrown in the block
}
catch(...) {
res.release(); // release the resource
throw;
}
res.release(); //skipped if exception is thrown.
}
To guarantee that the resource is released rather than provide a specific catch clause for every possible
exception and it is also quite possible that we may not know all the exceptions that might be thrown, we
can use a catch-all clause.
A catch(...) is used in combination with a rethrow expression. Since catch clauses are examined in the
order they appear following the try block so if a catch(...) clause is used in combination with other catch
clauses, it must always be placed last in a list of handlers otherwise a compiler error is issued.

31. A non-static data member cannot be initialized explicitly in class body.


Eg:
class First {
private:
int memi = 0; //error!
static const int DefaultArraySize = 12; //OK
};
As class data members are initialized using constructors.

32. A class may contain multiple public, protected or private labeled sections.

33. Since friends are not members of class granting friendship, they are not affected by public, protected or
private section in which they are declared within the class body.
friend istream& operator>>(istream&, First&);
friend ostream& operator<<(ostream&, const First&);
A friend may be a namespace function, a member function of another previously defined class, or an
entire class.
Note: Friendship is not transitive.
34. class A; // class declaration
If a class that is declared but not defined, then only a pointer or reference to that class type can be
declared. A class can have data members that are pointers and references to its own class type.

35. if(this != &sObj) // test when objects are not same.

36. The class designers indicates which member functions donot modify the class object by declaring them
as const member function.
class Screen {
public:
char get() const { return _screen[_cursor]; }
};
Only member functions declared as const can be invoked for a class object that is const.
Note: If a class contains pointers, the objects to which pointer refer may be modified within a const
member function. Eg:
private:
char* _text;
void Text::bad(const string &param) const {
_text = param.c_str(); // error: text cannot be modified.
for(int i = 0;i < param.size();i++)
_text[i] = param[i]; //OK: bad programming style!
}
Thus, const member function does not guarantee that everything to which class object refers will remain
invariant throughout invocation of the member function.

37. A const member function can be overloaded with a non-const member function that has same
parameter list.
Eg:
class Screen {
public:
char get(int,int);
char get(int,int)const; //overloaded
};
In this case, constness of object determines which of the two function is invoked.
int main() {
const Screen cs;
Screen s;

char ch = cs.get(0,0); //calls const member


ch = s.get(0,0); // calls non-const member
}
Note: constructors and destructors are exceptions in that, even though they are never a const member
function, they can be called for const class objects.

38. In general, data members of a const object cannot be modified. To allow a class data member to be
modified even when it is data member of a const object, we declare the data member as mutable.
Eg: mutable string::size_type _cursor;

void Screen::move(int r, int c) const {


//...
_cursor = row + c -1; //OK!
//...
}

39. Each class member function contains a pointer that addresses the object for which the member function
is called -- this.

40. Unlike other data members where each class object has its own copy, there is only one copy of a static
data member per class type and is a single, shared object accessible to all objects of its class type.
In general, a static data member is initialized outside the class definition.
class Account {
public:
Account(double,const string&);
string owner() { return _owner;}
private:
static double _interestRate;
double _amount;
string _owner;
};
double Account::_interestRate = 0.0589;

A const static data member can be initialized within the class body with a constant value. But if the static
const data member is of array type initialization must be made outside the class body otherwise it results in
a compile-time error. Eg:
class Account {
private:
static const int nameSize = 16;
static const char name[nameSize];
};
const int Account::nameSize;
const char Account::name[nameSize]="Savings Account";
A static data member can be of same class type as that of which it is a member.
class Bar {
private:
static Bar mem1; //OK
Bar* mem2; //OK
Bar mem3; // error!
};

41. A static data member can appear as a default argument to a member function of a class but a non-static
member cannot.

42. The static members of a class can be accessed using scope resolution operator.
43. A pointer to a member function:- using ->* and .* operators.
int (Screen::*pmfi)() = &screen.height;
Screen myScreen,*bufScreen;
(myScreen.*pmfi)(); // equivalent to invoking myScreen.height()
(bufScreen->*pmfi)(); // equivalent to invoking bufScreen->height()

44. Unions:
Special kind of class. The storage allocated for a union is the amount necessary to contain its largest data
member. Only one member at a time may be assigned a value. Members are public by default (like in
struct).
i> A union can not have a static data member or a member that is a reference.
ii> It also cannot have a member of a class type that defines either a constructor, destructor or a copy
assignment operator.
iii> Member functions, including constructors and destructors can defined for a union.
iv> A good practice, when handling a union object that is a class member provides a set of access
functions for each union data type.

45. We dont necessarily need a constructor and destructor to be defined for a class when all its data
members are public. It is possible that we dont provide default constructors but provide other constructors
taking one or more arguments. But container classes and allocation of a dynamic array of class objects
requires either a default constructor or no constructor at all. As a rule of thumb, it is almost always
necessary to provide a default constructor if other constructors are being defined as well.

46. For initialization of class members: the member initialization list, a comma separated list of a member
name and its initial value is used.
Eg: inline Account::Account():_name(0),_balance(0.0),_account_no(0){}

47. A constructor is never declared const or volatile.

48. By default, a single parameter constructor (or a multiple parameter constructor with default values
for all but the first parameter) serves as a conversion operator.Eg:
extern void print(const Account& acct);
int main() {
print("OOPS"); //implicit conversion
}
Converts "OOPS" into an Account object using Account::Account("OOPS",0.0) constructor where the
second parameter was a default parameter with value 0.0.
Unintended implicit class conversions prove a source of difficult to trace program errors. So, "explicit"
modifier informs compiler not to provide implicit conversions. explicit can only be used with constructors.
Eg: explicit Account(const char*, double=0.0);

49. Objects declared static are guaranteed to have their members zeroed out. Eg:
static Account acct1;

50. Not every class requires a destructor. Destructors serve primarily to relinquish resources acquired
either within the constructor or during the lifetime of the class object, again such as freeing a mutual
exclusion lock or deleting memory allocated through new operator.

51. Placement operator new allows us to construct a class object at a specific, pre-allocated memory
address.Eg:
char* arena = new char[sizeof Image];
Image* ptr = new (arena)Image("Quasimodo");
ptr is asssigned address associated with arena.
52. With a global overloaded operator, the parameter that represents the left operand must be specified
explicitly. Eg:
bool operator==(const string&,const string&);

53. For built-in types, four predefined operators (+,-,*,&) serve as both unary and binary operators. Either
or both arities of these operators can be overloaded.

54. Default arguments for overloaded operators are illegal, except for operator '()'.

55. If a function manipulates objects of two distinct class types, and the function needs to access the non-
public members of both classes, the function may either be declared as a friend to both classes or made a
member function of one class and a friend to other. A member function of a class cannot be declared as a
friend of another class until its class definition has been seen.
class Window;
class Screen {
public:
Screen& copy(Window&);
//...
};
class Window {
friend Screen& Screen::copy(Window&);
//...
};

56. Assignment operators associate right to left.


String verb,noun;
verb = noun = "count";
First the assignment operator,
string& operator=(const char*);
is called. Since the return type is reference to string object its return value is used to call copy assignment
operator,
string& operator=(const string&);
In the first place, "count" is a C style character string which has type const char* so we must make the
parameter of assignment operator accepting a c_style character string as const char*.

57. An overloaded operator() must be declared as a member function.

58. Smart Pointer: The member access operator -> must be defined as a class member function. It is
defined to give a class type a pointer like behaviour, most often used with class type that represents a
"smart pointer" like behaviour i.e. a class behaves very much like a built-in pointer type, but that supports
some additional functionality. Eg:
class ScreenPtr {
private:
Screen* ptr;
public:
ScreenPtr(const Screen& s):ptr(&s){}
Screen& operator*() {return *ptr;}
Screen* operator->() {return ptr;}
};
It cannot refer to no object as does a built-in pointer. So, we cannot define a default constructor.
Screen myScreen(4,4);
ScreenPtr ps(myScreen); //OK
ps->move(2,3);
The return type of an overloaded member access operator arrow must either be a pointer to a class type or
an object of a class for which the member access operator arrow is defined.
Auto Pointer:
auto_ptr
template<class T>
class auto_ptr {
public:
typedef T element_type;
explicit auto_ptr(T *p = 0) throw();
auto_ptr(const auto_ptr<T>& rhs) throw();
auto_ptr<T>& operator=(auto_ptr<T>& rhs) throw();
~auto_ptr();
T& operator*() const throw();
T *operator->() const throw();
T *get() const throw();
T *release() const throw();
};
The class describes an object that stores a pointer to an allocated object of type T. The stored pointer must
either be null or designate an object allocated by a new expression. The object also stores an ownership
indicator. An object constructed with a non-null pointer owns the pointer. It transfers ownership if its stored
value is assigned to another object. The destructor for auto_ptr<T> deletes the allocated object if it owns it.
Hence, an object of class auto_ptr<T> ensures that an allocated object is automatically deleted when
control leaves a block, even via a thrown exception.

59. Screen& operator++(); //prefix


Screen& operator++(int); //postfix
The int for postfix is left unnamed as compiler provides a default value for it.

60. A class may assume its own memory management by providing class member operators called
operator new() and delete(). These member operators can then take the place of the global operators to
allocate and deallocate objects of their class type. A class member operator new() must have a return type
of void* and take first parameter of size_t, where size_t is a typedef defined in system header file
<stddef.h>.
Eg:
class Screen {
public:
void* operator new(size_t);
void operator delete(void*);
};
Screen* ps = new Screen; // invokes the overloaded new operator
implemented by Screen class.
Screen* ps = ::new Screen; // invokes the global new operator
explicitly.
The class member operator delete() must have a void return type and a first parameter of type void*.
delete ps;
For selective invocation of global delete() operator,
::delete ps;
In object oriented class hierarchy in which operator delete() may be inherited by a derived class, we may
need,
void operator delete(void*,size_t);
Similarly, for arrays,
void* operator new[](size_t);
Screen* ps = new Screen[10];

void operator delete[](void*);


delete[] ps;
61. A conversion function is declared in the class body of specifying the keyword operator followed by the
type that is the target type of conversion. The general form is,
operator type();
where, type can be a built-in type, a class type or a typedef name. Conversion functions in which type
represents either an array or a function type are not allowed. A conversion function must be a member
function. Its declaration must not specify a return type nor can a parameter list be specified.
#include "SmallInt.h"
typedef char* tname;
class Token {
public:
Token(char*,int);
operator SmallInt(){return val;}
operator tName(){return name;}
operator int() {return val;}
private:
SmallInt val;
char* name;
};
Token::operator int() is applied implicitly by the compiler to convert an object of type Token to
a value of type int.
Token tok("function",78);
SmallInt tokVal = SmallInt(tok); // invokes Token::operator SmallInt().
An explicit cast may cause a conversion function to be invoked. If the value converted is of class type
that has a conversion function and the type of the conversion is that specified by the cast, then the class
conversion function is called.Eg:
int val = static_cast<int> SmallInt(tok);
invokes <i> Token::operator SmallInt()
<ii> SmallInt::operator int()
If target of conversion doesnot match the type of the conversion function exactly, a conversion function
can still be invoked if the target type can be reached through a standard conversion sequence. Eg:
extern void calc(double);
Token tok("constant",44);
calc(tok);
The last statement invokes Token::operator int() then Token::operator SmallInt() and then int is converted
to double by compiler.

62. The collection of constructors of a class taking a single parameter define a set of implicit conversion
from values of the constructor's parameter types to values of class type. Eg:
SmallInt(int) converts values of type int to SmallInt values. The constructor is called by the
compiler to create a temporary object of type SmallInt.
A copy of value of this temporary object is passed to calc. The temporary object is then destroyed at the
end of function call statement. If necessary, a standard conversion sequence is applied to an argument
before a constructor is called to perform a user-defined conversion. Eg:
extern void calc(SmallInt);
calc(dval);
dval is converted to int and SmallInt::SmallInt(int) is invoked.
But if we want that compiler should never invoke implicitly a constructor with single parameter to
perform type conversion rather the constructor must only be invoked to initialize an object only then we
may write,
explicit SmallInt(int);
The compiler never uses an explicit constructor to perform implicit type conversions. But conversions
are possible if requested explicitly in the form of casts.
calc(i); // error: no implicit conversion from int to SmallInt
calc(static_cast<SmallInt>(i)); //OK
63. In C++, a special type/subtype relationship exists in which a base class pointer or reference can address
any of its derived class subtypes without programmer intervention. This ability to manipulate more than
one type with a pointer or a reference to a base class is spoken of as "polymorphism". Eg:
void lookAt(const Camera* pCamera);
Each individual invocation of lookAt() passes in the address of an object of one Camera subtypes. It is
converted automatically by compiler to appropriate base class pointer.Eg:
lookAt(&oCam);
Thus, subtype polymorphism allows us to write the kernel of our application independent of individual
types we wish to manipulate. If we later wish to add or remove a subtype, our implementation of lookAt()
need not change. Rather, we program the public interface of the base class of our abstraction through base
class pointers and references. At run-time, the actual type being referenced is resolved and the appropriate
instance of the public interface is invoked. The run-time resolution of the appropriate function to invoke is
termed "dynamic binding". This is supported using class "virtual functions".

64. Two primary disadvantages of explicit programmer management of type resolution are:
i> the increased size and complexity of code
ii> the difficulty of adding to or removing from supported set of types without breaking existing code.
In object oriented programming, the burden shifts from programmer to compiler.

65. The primary benefit of an inheritance hierarchy is that we can program to the public interface of the
abstract class rather than to the individual types that form its inheritance hierarchy, in this way shielding
our code from changes in that hierarchy.

66. "Polymorphism" is the ability of a pointer or a reference of a base class to address any of its derived
classes. Eg:
void lookAt(cont Camera* pCamera) {
pCamera->lookAt();
}
pCamera->lookAt() must invoke the appropriate lookAt() virtual member function based on the actual class
object pCamera addresses. So the programmer manipulates an unknown instance from infinite set of types
bound by its inheritance hierarchy. This is achieved through the manipulation of objects through base class
pointers and references only.

67. Polymorphism is supported in three ways:


i> Through imlicit conversion of a derived class pointer or reference to a pointer or reference of its
public base type.
Query* pQuery = new NameQuery("Glass");
ii> Through virtual function mechanism.
iii> Through dynamic_cast and typeid operators.
if(NameQuery* pnq = dynamic_cast<NameQuery*>(pQuery))...

68. The three derivation types are: public, protected and private.
A) Public Derivation:- All public and protected base members and methods are accessible to derived
class. The public and protected become public and protected in the subclass, respectively. Also any function
can convert Derived* to Base*.
Eg:
class X {
public:
int a;
};
class Y1:public X {};
class Y2:protected X {};
class Y3:private X {};
void f(Y1* py1, Y2* py2, Y3* py3) {
X* px = py1; //1.OK
py1->a = 7; //2.OK
px = py2; //3.Error
py2->a = 7; //4.Error
px = py3; //5.Error
py3-> = 7; //6.Error
}
Explanation:
1. A function f() in global namespace can convert a publicly derived class pointer to a base class pointer.

2. The public member a of X is also treated as public member of a publicly derived class py1. And any
function can refer to a class' public member directly.
3. A global namespace function f() cannot convert protectedly derived class pointer to a base class
pointer.
4. The public member of X is treated as protected member of Y2 class and so cannot be accessed by f().
5. f() cannot convert a privately derived class Y3 pointer to base class pointer.
6. The public member of X is treated as a private member of Y3 class and so cannot be accessed by f().

B) Protected Derivation:- public and protected in base class becomes protected in subclass. Only
members, friends and subclass of subclass can convert from Derived* to Base*.

C) Private Derivation:- public and protected in base class becomes private in subclass.Only members and
friends of subclass can covert from Derived* to Base*.

69. The forward declaration of a derived class does not include its derivation list, but simply the class
name.Eg: class NameQuery;

70. Abstract base class are used only to define base class pointers and references used for indirect
manipulation of objects of the class types derived from it.

71. The members of an abstract class represent:-


i>The set of operations supported by all the derived class types. This includes both virtual and non-
virtual operations that are shared among the derived classes.
ii>The set of data members common to the derived classes. By factoring these members out of the
derived classes into our abstract base class, we are able to access the members independent of the actual
type on which we are operating.
For every virtual function in abstract base class each derived class may provide its own implementation.
This becomes a must for pure virtual functions.

72. When a virtual function has no meaningful algorithm to define in the abstract base class it is made a
"pure virtual function".
virtual void eval() = 0;
It serves as a placeholder in the public interface of the class hierarchy. It is not intended ever to be
invoked within our program. In the derived class we just mention,
void eval();
or virtual void eval();
The derived class instance of an inherited virtual function does not need to but may specify the virtual
keyword. The compiler recognizes the instance based on a comparison of the function prototype.

73. The base class and derived class member functions donot make up a set of overloaded functions. Eg:
class Diffident {
public:
void mumble(int);
};
class Shy:public Diffident {
public:
void mumble(string);
};
Shy::mumble(string) lexically hides visibility of Diffident::mumble(int) but they donot form a pair of
overloaded function. In general, the overloaded candidate functions of a name must all occur in the same
scope. Under standard C++, the using declaration creates an overloaded set of base and derived class
members.
class Shy:public Diffident() {
public:
void mumble(string);
using Diffident::mumble;
};

74. A derived class may directly access the protected data member of its base class but only for one base
class subobject: its own base class subobject. The derived class doesnot have access to the protected
members of an independent base class object or for that matter, any derived cannot access the protected
data members of its superclass for any other superclass object other than its own.

75. Often member access problems between a derived and base class can be resolved by moving the
operation to the class that contains that inaccessible member.

76. In C++, a base class pointer can only access the data members and member functions, declared or
inherited within its class regardless of the actual object it may address. That is, except for the virtual
function declared in the base class and overridden in the derived class, there is no way to access a derived
class member directly though a base class pointer.

77. If a derived class wishes to access the private member of its base class directly, the base class must
declare the derived class explicitly to be a friend. Friendship is not inherited ie subclass of a derived class
(granted friendship by its base class) doesnot inherit the friendship of the derived class.

78. The order of constructor invocation:-


i> The base class constructor. If there is more than one base class (multiple inheritance) the constructors
are invoked in the order the base classes appear in the class derivation list, and not in the order in which
they are listed in the member initialization list.
ii> Member class object constructor. If there is more than one member class object, the constructors are
invoked in the order in which the objects are declared within the class, and not in the order in which they
are listed in the member initialization list.
iii> The derived class constructor.
Eg:
class NameQuery:public Query {
public:
NameQuery(const string& name,vector<location>*
ploc):_name(name),Query(*ploc),_present(true){}
protected:
bool _present;
string _name;
};
The order of constructor call for the above example is:
1. Query::Query(vector<location>*);
2. string::string(const string&);
3. NameQuery::NameQuery(const string&,vector<location>*);
In all three constructors are called. As a general rule, the derived class constructor should never assign a
value to a base class data member directly, but rather pass the value to the appropriate base class
constructor otherwise the implementations of the two classes become tightly coupled, and it can be more
difficult to modify or extend the base class implementation correctly.

79. As an abstract base class object is only intended to exist in our program as a subobject within a class
object of one of its derived subtypes we can declare an abstract base class constructor as protected or
private rather than public.Eg:
class Query {
public:
//...
protected:
Query();
Query(const vector<location>& loc);
//...
};

80. Derived class constructors are made public as actual derived class objects are expected to be defined.
Each derived class must provide its own set of constructors even if the constructors serve no other purpose
than to provide an interface by which to pass the operands to the base class constructor. A derived class can
invoke legally only the constructor of its immediate base class. It is illegal for OrQuery in the example
below to invoke Query class constructor as Query class is not an immediate base class for it.
class BinaryQuery:public Query {
protected:
BinaryQuery(Query* lop,Query* rop):_lop(lop),_rop(rop){}
Query* lop;
Query* rop;
};

class OrQuery:public BinaryQuery {


public:
OrQuery(Query* lop,Query* rop):BinaryQuery(lop,rop){}
virtual void eval();
};
Three constructors are invoked when an OrQuery type object is instantiated:
i> Query non-immediate base class constructor
ii>BinaryQuery immediate base class constructor
iii> OrQuery constructor.
The invocation order is a depth first traversal of the inheritance hierarchy.

81. Destructors: When the lifetime of a derived class object ends, the derived and base class destructors, if
defined, are invoked automatically as well as the destructors of any member class objects. The order of
destructor invocations for a derived class object is the reverse of its constructor order of invocation.
Eg: NameQuery nq("hyperion");
The order of destructor invocation is:
i> NameQuery::~NameQuery();
ii> string::~string();
iii> Query::~Query();
If within the derived class destructors, the delete expression is applied to a base class pointer (but then
we dont want base class destructor to be invoked), we must declare our base class destructor to be virtual.

82. When a member function is virtual, the function invoked is the one defined in the dynamic type of the
class pointer or reference through which it is invoked. The virtual function mechanism works only when
used with pointers and references. Polymorphism is enabled only when a derived class subtype is addressed
indirectly through either a base class reference or pointer. The use of base class object instead of pointer or
reference doesnot preserve the type-identity of the derived class.
NameQuery nq("lilacs");
Query q = nq; //OK
NameQuery portion of nq is sliced-off prior to initialization of q. This is one of the ironies of OOPS in C++
that we must use pointers and references but not objects to support it!

83. The base class first introducing a virtual function must specify the virtual keyword within the class
declaration. If the definition is placed outside the class, the keyword virtual must not again be specified.
The derived class instance of a virtual function, if defined, is said to override the inherited base class
instance. If not defined, it inherits the active base class instance.

84. In order for a derived class instance of a virtual function to override the instance active in its base class,
its prototype must match that of the base class exactly. The return value of the derived instance can be a
publicly derived class type of the type of the return value of the base instance.Eg: If the base class instance
of a virtual function returns a Query* then the derived instance of the virtual function can return a
NameQuery*.

85. We cannot provide a virtual output operator<< directly since the output operators are already members
of ostream class. Instead, we must provide an indirect virtual function:-
inline ostream& operator<<(ostream& os,const Query* pq) {
return pq.print(os);
}
where, print() is virtual.
A class containing (or inheriting) one or more pure virtual functions is recognised as an abstract base class
by the compiler. Any attempt to create an independent class of object of an abstract base class results in a
compile-time error. Similarly, it is an error to invoke a pure virtual function through the virtual mechanism.

86. When we invoke a virtual function using class scope operator, we override the virtual mechanism,
causing the virtual function to be resolved statically at compile-time. Within a derived class virtual function
it may sometimes be necessary to invoke a base class instance to complete an operation that has been
factored across the base and derived instances.

87. Virtual Base Class: Because a class can be an indirect base class to a derived class more than once,
C++ provides a way to optimize the way such base classes work. Consider the class hierarchy in Figure 9.5,
which illustrates a simulated lunch line.

Figure 9.5 Simulated Lunch-Line Graph

In Figure 9.5, Queue is the base class for both CashierQueue and LunchQueue. However, when both
classes are combined to form LunchCashierQueue, the following problem arises: the new class contains
two subobjects of type Queue, one from CashierQueue and the other from LunchQueue. Figure 9.6 shows
the conceptual memory layout (the actual memory layout might be optimized).

Figure 9.6 Simulated Lunch-Line Object


Note that there are two Queue subobjects in the LunchCashierQueue object. The following code declares
Queue to be a virtual base class:

class Queue
{
// Member list
};

class CashierQueue : virtual public Queue


{
// Member list
};

class LunchQueue : virtual public Queue


{
// Member list
};

class LunchCashierQueue : public LunchQueue, public CashierQueue


{
// Member list
};

The virtual keyword ensures that only one copy of the subobject Queue is included (see Figure 9.7).

Figure 9.7 Simulated Lunch-Line Object with Virtual Base Classes

A class can have both a virtual component and a nonvirtual component of a given type. This happens in the
conditions illustrated in Figure 9.8.

Figure 9.8 Virtual and Nonvirtual Components of the Same Class


In Figure 9.8., CashierQueue and LunchQueue use Queue as a virtual base class. However, TakeoutQueue
specifies Queue as a base class, not a virtual base class. Therefore, LunchTakeoutCashierQueue has two
subobjects of type Queue: one from the inheritance path that includes LunchCashierQueue and one from
the path that includes TakeoutQueue. This is illustrated in Figure 9.9.

Figure 9.9 Object Layout with Virtual and Nonvirtual Inheritance

Note Virtual inheritance provides significant size benefits when compared with nonvirtual inheritance.
However, it can introduce extra processing overhead.

If a derived class overrides a virtual function that it inherits from a virtual base class, and if a constructor or
a destructor for the derived base class calls that function using a pointer to the virtual base class, the
compiler may introduce additional hidden “vtordisp” fields into the classes with virtual bases. The /vd0
compiler option suppresses the addition of the hidden vtordisp constructor/destructor displacement
member. The /vd1 compiler option, the default, enables them where they are necessary. Turn off vtordisps
only if you are sure that all class constructors and destructors call virtual functions virtually.

The /vd compiler option affects an entire compilation module. Use the vtordisp pragma to suppress and
then reenable vtordisp fields on a class-by-class basis:

#pragma vtordisp( off )


class GetReal : virtual public { ... };
#pragma vtordisp( on )
Multiple inheritance introduces the possibility for names to be inherited along more than one path. The
class-member names along these paths are not necessarily unique. These name conflicts are called
“ambiguities.”

Any expression that refers to a class member must make an unambiguous reference. The following example
shows how ambiguities develop:

// Declare two base classes, A and B.


class A
{
public:
unsigned a;
unsigned b();
};

class B
{
public:
unsigned a(); // Note that class A also has a member "a"
int b(); // and a member "b".
char c;
};
// Define class C as derived from A and B.
class C : public A, public B
{
};

Given the preceding class declarations, code such as the following is ambiguous because it is unclear
whether b refers to the b in A or in B:

C *pc = new C;

pc->b();

Consider the preceding example. Because the name a is a member of both class A and class B, the compiler
cannot discern which a designates the function to be called. Access to a member is ambiguous if it can refer
to more than one function, object, type, or enumerator.

The compiler detects ambiguities by performing tests in this order:

If access to the name is ambiguous (as just described), an error message is generated.

If overloaded functions are unambiguous, they are resolved. (For more information about function
overloading ambiguity, see Argument Matching in Chapter 12.)

If access to the name violates member-access permission, an error message is generated. (For more
information, see Chapter 10, Member-Access Control.)
When an expression produces an ambiguity through inheritance, you can manually resolve it by qualifying
the name in question with its class name. To make the preceding example compile properly with no
ambiguities, use code such as:

C *pc = new C;

pc->B::a();

Note When C is declared, it has the potential to cause errors when B is referenced in the scope of C. No
error is issued, however, until an unqualified reference to B is actually made in C’s scope.

If virtual base classes are used, functions, objects, types, and enumerators can be reached through multiple-
inheritance paths. Because there is only one instance of the base class, there is no ambiguity when
accessing these names.

Figure 9.10 shows how objects are composed using virtual and nonvirtual inheritance.
Accessing any member of class A through nonvirtual base classes causes an ambiguity; the compiler has no
information that explains whether to use the subobject associated with B or the subobject associated with C.
However, when A is specified as a virtual base class, there is no question which subobject is being
accessed.
It is possible for more than one name (function, object, or enumerator) to be reached through an inheritance
graph. Such cases are considered ambiguous with nonvirtual base classes. They are also ambiguous with
virtual base classes, unless one of the names “dominates” the others.

A name dominates another name if it is defined in both classes and one class is derived from the other. The
dominant name is the name in the derived class; this name is used when an ambiguity would otherwise
have arisen, as shown in the following example:

class A
{
public:
int a;
};

class B : public virtual A


{
public:
int a();
};

class C : public virtual A


{
...
};

class D : public B, public C


{
public:
D() { a(); } // Not ambiguous. B::a() dominates A::a.
};

Vous aimerez peut-être aussi