Cpp Notes

  • Uploaded by: Watsh Rajneesh
  • 0
  • 0
  • October 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Cpp Notes as PDF for free.

More details

  • Words: 10,659
  • Pages: 30
C++ Notes Compiled by: Watsh Rajneesh Software Engineer @ Quark (R&D Labs) 4/4/2002 [email protected] 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 Stack { public: Stack(); ~Stack();

void push(const T& object); T pop(); bool empty() const;

// is stack empty?

private: struct StackNode { T data; StackNode *next;

// linked list node // data at this node // 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); Stack& operator=(const Stack& rhs);

Stack::Stack(): top(0) {}

// initialize top to null

template void Stack::push(const T& object) { top = new StackNode(object, top); } template T Stack::pop() { StackNode *topOfStack = top; top = top->next; T data = topOfStack->data; delete topOfStack;

// prevent copying and // assignment

// put new node at // front of list

// remember top node

// remember node data

return data; } Stack::~Stack() { while (top) { StackNode *toDie = top; top = top->next; delete toDie; } }

// delete all in stack // get ptr to top node // move to next node // 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 #include using namespace std; #include #include using namespace std;

template 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 void LinkedList::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 void LinkedList::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 void LinkedList::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 T LinkedList::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 list; list.put(2); list.put(3); list.put(4); list.print(); LinkedList 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 delete [] _name; _name = new char[strlen(p._name)+1]; strcpy(_name, p._name); _id = p._id; } return *this; // return ref for multiple }//end operator=

object // free old name's memory. // get new space // copy new name // copy id assignment

4. The manipulator "endl" inserts a newline character into the output stream and then flushes the o/p buffer. Its a part of . 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 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 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 Array {...}; template void print(Array<eType>*); class String {...}; template class List {...}; }; using namespace Exerice; int main() { const int size = 1024; Array<String> as(size); List il(size); //... Array<String> *pas = new Array<String> (as); List *pil = new List (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 vec1; // empty vector vector vec2(size,value); //each element=value int ia[4]={0,1,2,3}; vector vec3(ia,ia+4); // elements = 0,1,2,3 vector vec4(size); //elements = 0 vector 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 vec(size); vector::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 . Standard library also provides support for a map associative array -- elements of which can be indexed by something other than integer values. Eg: template 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 . 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(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(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&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 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 //ok T1 min(T2,T3); The name of the template parameter can be used only once with same template type. Eg: template // error

To declare a function template as inline: template inline Type min(Type, Type); A function template is instantiated either when it is invoked or when its address is taken. template 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) { //... eObj = negativeOp; throw; // original exception object rethrown }

catch(EHState &eObj) { //... eObj = negativeOp; throw; // exception rethrown has value // 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 ¶m) 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 nonpublic 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 auto_ptr { public: typedef T element_type; explicit auto_ptr(T *p = 0) throw(); auto_ptr(const auto_ptr& rhs) throw(); auto_ptr& operator=(auto_ptr& 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 deletes the allocated object if it owns it. Hence, an object of class auto_ptr 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 SmallInt(tok); invokes Token::operator SmallInt() 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(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 nonvirtual 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* 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*); 2. string::string(const string&); 3. NameQuery::NameQuery(const string&,vector*); 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& 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 multipleinheritance 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. };

Related Documents

Cpp Notes
October 2019 30
Cpp
May 2020 23
Cpp
December 2019 37
Cpp
June 2020 25
Cpp
October 2019 39
Cpp Slides
November 2019 31

More Documents from ""

Understanding Snmp Stack
September 2019 37
Corba Using Cpp
September 2019 51
Sql Notes
September 2019 28
Cpp Notes
October 2019 30
C Programming In Unix
September 2019 39
Asked Question On Unix
October 2019 44