Escolar Documentos
Profissional Documentos
Cultura Documentos
1 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Contents
1 Inheritance Peculiarities in C++
1.1 Inheritance Kinds
1.2 No Member Initialization
1.3 Default Dispatch Type is Static Dispatch
1.4 Hide-by-name Overloading
1.5 Multiple Inheritance, No Root Class
1.6 Test Program
2 Inheritance a la Java
2.1 Root Class and the Interface Concept
2.2 Module
2.2.1 Interface (Rational)
2.2.2 Implementation (Rational)
2.2.3 Interface (Whole)
2.2.4 Implementation (Whole)
2.3 Exception Classes
2.4 Test Program
3 Private Inheritance
3.1 Interface (List)
3.2 Implementation (List)
3.3 Interface (Stack)
3.4 Implementation (Stack)
4 Virtual Inheritance
5 Implementing Polymorphism
5.1 vtables
5.2 Adjustor Thunks
5.3 Complications due to Multiple Inheritance
6 Constructor and Destructor Call Order
6.1 An Object with Primitive Type Fields
6.2 An Object Composed of Sub-object(s)
6.3 Creating an Array of Objects
6.4 Inheritance
6.5 Member Initialization List
6.6 Multiple Inheritance
6.7 Restating the Formula: Inheritance is Compiler-Managed Composition
6.8 Virtual Inheritance
7 Notes
20-11-2014 13:08
2 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
This innocent-looking code claims to derive D from B. However, it will not let you treat an object of D as an object of B. Thats
right: D is not seen as a subtype of B. It looks like either C++ or the programmer got it wrong. Not exactly! Similar to the default
section being private, inheritance, unless otherwise stated, is taken to be of the so-called private kind. Deferring the answer of
what we mean by private inheritance to another section, we modify the above code to meet our expectations as given below.
class D : public B { ... }
Done with this peculiarity, lets move on to see some code examples. We will be using the following class definitions throughout
the examples.
class B {
public:
B(void) { }
void f1s(void) { cout << "In B::f1s(void)" << endl; }
virtual void f1d(void) { cout << "In B::f1d(void)" << endl; }
virtual void f2(int i) { cout << "In B::f2(int)" << endl; }
...
}; // end of class B
class D : public B {
public:
D(void) { }
void f1s(void) { cout << "In D::f1s(void)" << endl; }
virtual void f1d(void) { cout << "In D::f1d(void)" << endl; }
virtual void f2(short s) { cout << "In D::f2(short)" << endl; }
...
int _m_i;
short _m_s;
}; // end of class D
No Member Initialization
...
D* d = new D();
cout << d->_m_i << " " << d->_m_s << endl;
...
As a Java programmer you might think the above code fragment should produce two 0's in succession. However, it will output
two random values. Unlike Java and C# where, unless overridden, data members are provided with default initial values, C++
compiler does not initialize the data members. If they need to be initialized to 0 or to any other value, this must be done explicitly
by the programmer. It should also be noted that one cannot declare a data member with an initial value. That is, changing int
_m_i; to int _m_i = 0; in D will give rise to a syntactic error.
D() : _m_i(0), _m_s(0) { } or D() { _m_i = 0; _m_s = 0; }
The C++ compiler has full confidence in the programmer. After all, a C++ programmer does not make a mistake. An error prone
statement, which may be seen as the begetter of a hideous mistake in Java, is assumed to be the conscious decision of an
all-knowing programmer. Its not a bug its a feature!
20-11-2014 13:08
3 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
...
B* b;
if (bool_expr) b = new D();
else b = new B();
b->f1d();
...
A virtual function is dispatched dynamically. So, unlike the previous one, this example will compile and yield an output
depending on the value of bool_expr. If it evaluates to true it will output "In D::f1d(void)", otherwise it will output "In
B::f1d(void)".
Hide-by-name Overloading
...
D* d = new D();
int i = 3;
d->f2(i); // will be dispatched to D::f2(short)
...
With Java semantics, above code outputs "In B::f2(int)". After all, d is also an object of type B and can use the public interface
of B just like a genuine B object. So, both D::f2(short) and B::f2(int) are exposed to clients of d. Not in C++! Unlike
Java, where base and derived class member functions make up a set of overloaded functions, C++ restricts this set to a single
scope. Since derived and base classes are different scopes, any derived class function with a name coinciding with a base class
function will shadow all the functions in the base class. Technically, we say C++ hides by name while Java is said to hide by
signature.
But doesnt it go against the logic of inheritance? You claim d to be an object of B (through the public inheritance relationship)
and dont let its clients use some function appearing in the public interface of B? Thats right and C++ provides means to meet
your expectations.
Example: Delegation
class B {
public:
...
void f2d(int i) { cout << "In B::f2d(int)" << endl; }
...
}; // end of class B
class D : public B {
public:
...
void f2d(short s) { cout << "In D::f2d(short)" << endl; }
Regardless of whether the call is to some virtual function or not, explicit use of the class name in the function call causes it to be
dispatched statically. In the following function, for instance, [although this is of type D* and f2d(int) is virtual]
function call in the second statement will be dispatched to B::f2d(int).
Note this type of static dispatch can be used to invoke any function in the receiver objects class or any function in any one of
the ancestor classes.
void f2d(int i) { cout << "Delegating..."; B::f2d(i); }
...
}; // end of class D
...
D* d = new D();
int i = 3;
d->f2d(i); // will be delegated to B::f2d(int) through D::f2d(int)
short s = 5;
d->f2d(s); // will be dispatched to D::f2d(short)
...
Example: using declaration
20-11-2014 13:08
4 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
class B {
public:
...
void f2u(string s) { cout << "In B::f2u(string)" << endl; }
void f2u(void) { cout << "In B::f2u(void)" << endl; }
}; // end of class B
class D : public B {
public:
...
void f2u(float f) { cout << "In D::f2u(float)" << endl; }
using B::f2u;
...
}; // end of class D
...
D* d = new D();
float f = 3.0;
d->f2u(f); // will be dispatched to D::f2u(float)
string s = string("abc");
d->f2u(s); // will be dispatched to B::f2u(string)
d->f2u(); // will be dispatched to B::f2u(void)
Test Program
Peculiarities.cxx
1.
#include <iostream>
2.
#include <string>
3.
using namespace std;
4.
5.
namespace CSE224 {
6.
namespace DS {
7.
class B {
8.
public:
9.
B(void) { }
10.
void f1s(void) { cout << "In B::f1s(void)" << endl; }
11.
virtual void f1d(void) { cout << "In B::f1d(void)" << endl; }
12.
virtual void f2(int i) { cout << "In B::f2(int)" << endl; }
20-11-2014 13:08
5 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
13.
virtual void f2d(int i) { cout << "In B::f2d(int)" << endl; }
14.
virtual void f2u(string s) { cout << "In B::f2u(string)" << endl; }
15.
virtual void f2u(void) { cout << "In B::f2u(void)" << endl; }
16.
}; // end of class B
17.
18.
class D : public B {
19.
public:
20.
D(void) { }
21.
void f1s(void) { cout << In D::f1s(void) << endl; }
22.
virtual void f1d(void) { cout << In D::f1d(void) << endl; }
23.
virtual void f2(short s) { cout << In D::f2(short) << endl; }
24.
virtual void f2d(short s) { cout << In D::f2d(short) << endl; }
25.
virtual void f2d(int i) { cout << Delegating...; this->B::f2d(i); }
26.
virtual void f2u(float f) { cout << In D::f2u(float) << endl; }
27.
using B::f2u;
28.
29.
int _m_i;
30.
short _m_s;
31.
}; // end of class D
32.
} // end of namespace DS
33.
} // end of namespace CSE224
34.
35.
using namespace CSE224::DS;
36.
37.
void default_is_static_dispatch(void) {
38.
cout << "TESTING DEFAULT DISPATCH TYPE" << endl;
39.
cout << "b: Static type: B*, Dynamic type: D*" << endl;
40.
B* b = new D();
41.
cout << "Sending (non-virtual) f1s(void) to b..."; b->f1s();
42.
cout << "Sending (virtual) f1d(void) to b..."; b->f1d();
43.
} // end of void default_is_static_dispatch(void)
44.
45.
void call_delegation(void) {
20-11-2014 13:08
6 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
46.
cout << "Testing delegation..." << endl;
47.
D* d = new D();
48.
int i = 3;
49.
cout << "Sending (virtual) f2d(int) to d...";
50.
d->f2d(i);
51.
short s = 5;
52.
cout << "Sending (virtual) f2d(short) to d...";
53.
d->f2d(s);
54.
} // end of void call_delegation(void)
55.
56.
void using_declaration(void) {
57.
cout << "Testing the using declaration..." << endl;
58.
D* d = new D();
59.
float f = 3.0;
60.
cout << "Sending (virtual) f2u(float) to d...";
61.
d->f2u(f);
62.
string s = string(abc);
63.
cout << "Sending (virtual) f2u(string) to d...";
64.
d->f2u(s);
65.
cout << "Sending (virtual) f2u(void) to d...";
66.
d->f2u();
67.
} // end of void using_declaration(void)
68.
69.
void CPP_hides_by_name(void) {
70.
cout << "TESTING HIDE-BY NAME" << endl;
71.
D* d = new D();
72.
int i = 3;
73.
cout << "Sending (virtual) f2(int) to d...";
74.
d->f2(i);
75.
call_delegation();
76.
using_declaration();
77.
} // end of void CPP_hides_by_name(void)
78.
20-11-2014 13:08
7 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
79.
void no_member_initialization(void) {
80.
cout << "TESTING MEMBER INITIALIZATION" << endl;
81.
D* d = new D();
82.
cout << "_m_i: " << d->_m_i << " _m_s: " << d->_m_s << endl;
83.
} // end of void no_member_initialization(void)
84.
85.
int main(void) {
86.
no_member_initialization();
87.
cout << endl;
88.
default_is_static_dispatch();
89.
cout << endl;
90.
CPP_hides_by_name();
91.
92.
return 0;
93.
} // end of int main(void)
HIDE-BY NAME
(virtual) f2(int) to d...In D::f2(short)
delegation...
(virtual) f2d(int) to d...Delegating...In B::f2d(int)
(virtual) f2d(short) to d...In D::f2d(short)
the using declaration...
(virtual) f2u(float) to d...In D::f2u(float)
(virtual) f2u(string) to d...In B::f2u(string)
(virtual) f2u(void) to d...In B::f2u(void)
Inheritance a la Java
In this part of the handout, we provide an insight into how C++ and Java can be related as far as inheritance is concerned. This is
accomplished by simulating the concepts found in Java using those found in C++. Such an approach should not be taken as an
advertisement campaign of Java; needless to say Java is not without competition. It should rather be taken as an incomplete
attempt at providing clues to the inner workings of the mentioned concepts.
20-11-2014 13:08
8 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
In Java, expressing common attributes among unrelated objects is made possible by means of the root class and the interface
concept. The former defines a common denominator among all classes, while the latter is used to classify a group of classes.[1]
For instance, because it is listed in Object all objects can be tested for equality with an object of a compatible type; or objects
of classes claiming to be Comparable can be compared with a compatible object.
These two notions are not supported in C++ as a linguistic abstraction. Instead, programmers are expected to resort to using
conventions or simulate it through other constructs. For instance, testing for equality is accomplished by overriding the default
implementation of the == operator; interface concept, which is not directly supported, can be simulated by means of abstract
classes with pure virtual functions.
Object
1.
#ifndef OBJECT_HXX
2.
#define OBJECT_HXX
3.
4.
namespace System {
5.
class Object {
Our intention in having the header file Object is to define a root class that can be used as a polymorphic type in generic
functions, such as compareTo defined in IComparable; we do not mean to provide any shared functionality as is done in
Object class of Java. This, however, cannot be accomplished simply by defining an empty class. In order for a type to be
polymorphic in C++ it must have at least one virtual function. We therefore include a dummy virtual function in our class
definition.
But then, why did we make its access modifier protected? First of all, it cannot be public because we dont want any
functionality to be exposed through this class. What about declaring no_op to be private? After all, declaring it as
protected means deriving classes can now send the no_op message. Answer lies in the nature of polymorphism: In order for
polymorphism to be possible, one should be able to override the definition of a dynamically-dispatched function found in the
base class. This implies such functions should be open at least to the derived classes. As a matter of fact C++ compilers will not
even let you declare virtual functions in a <sourcelang="cpp" enclose="none">private</source> section.
6.
protected:
7.
virtual void no_op(void) { return; }
8.
}; // end of class Object
9.
} // end of namespace System
10.
11.
#endif
Definition: A pure virtual function is a virtual function that is not given a function body in the declaring class. A derived class
claiming to be concrete must therefore provide an implementation for such a function.
In terms of C++-supported concepts, an interface is a "fieldless" abstract class whose all functions are pure virtual.
IComparable
20-11-2014 13:08
9 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
1.
#ifndef ICOMPARABLE_HXX
2.
#define ICOMPARABLE_HXX
3.
4.
#include "Object"
5.
6.
namespace System {
7.
class IComparable {
8.
public:
9.
virtual int compareTo(const Object&) const = 0;
10.
}; // end of class IComparable
11.
} // end of namespace System
12.
13.
#endif
Module
Interface (Rational)
Rational
1.
#ifndef RATIONAL_HXX
2.
#define RATIONAL_HXX
3.
4.
#include <iostream>
5.
using namespace std;
6.
7.
#include "IComparable"
8.
#include "Object"
9.
using namespace System;
10.
11.
#include "math/exceptions/NoInverse"
12.
#include "math/exceptions/ZeroDenominator"
13.
20-11-2014 13:08
10 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
#include "math/exceptions/ZeroDivisor"
14.
using namespace CSE224::Math::Exceptions;
15.
16.
namespace CSE224 {
17.
namespace Math {
Having defined the interface concept as a variation on the class concept, we naturally should be cautious about speaking of the
implementation relation. This is indeed the case in C++: one can speak of the extension relation only. As a consequence support
for multiple inheritance is a must.
18.
class Rational : public Object, public IComparable {
19.
public:
20.
Rational(long num = 0, long den = 1) throw(ZeroDenominator) {
21.
if (den == 0) {
22.
cerr << "Error: ";
23.
cerr << "About to throw ZeroDenominator exception" << endl;
24.
throw ZeroDenominator();
25.
} // end of if (den == 0)
26.
_n = num;
27.
_d = den;
28.
this->simplify();
29.
} // end of constructor(long=, long=)
30.
31.
Rational(Rational& existingRat) {
32.
_n = existingRat._n;
33.
_d = existingRat._d;
34.
} // end of copy constructor
Notice the following functions, unlike the rest of the member functions, will be dispatched statically. In Java, such an effect can
be achieved by declaring methods to be final.
36.
long getNumerator(void) const { return _n; }
37.
20-11-2014 13:08
11 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
In addition to marking functions virtual, we also declare them to return a reference. This is because a reference is the best
candidate for serving the purpose of handles in Java: it is an inheritance-aware, compiler-managed pointer.[2] That is, we can
pass as argument to the next function [or any function expecting a reference to Rational, for that matter] an object belonging
in the class hierarchy rooted by the Rational class; dereferencing of a reference is automatically done by the compilersynthesized code.
As an alternativealthough the resulting code would be less writable and readablewe could have used a plain pointer.
However, using a plain object type is out of question. This is due to the fact that polymorphism together with inheritance requires
sending the same messagethat is what polymorphism is all aboutto objects of probably varying sizesenter inheritance
which in turn implies passing and returning variable-sized objects. This is something compilers cannot deal with! We should
provide some assistance, which we do by injecting a fixed-size programming entity in between: pointer or reference.
39.
virtual Rational& add(const Rational&) const;
40.
virtual Rational& divide(const Rational&) const throw(ZeroDivisor)
41.
virtual Rational& inverse(void) const throw(NoInverse);
42.
virtual Rational& multiply(const Rational&) const;
43.
virtual Rational& subtract(const Rational&) const;
44.
virtual int compareTo(const Object&) const;
Note the following function serves a purpose akin to that of toString in Java. Replacing sstream instead of ostream and
changing the implementation accordingly would make the analogy a more perfect one.
46.
friend ostream& operator<<(ostream&, const Rational&);
47.
48.
private:
49.
long _n, _d;
50.
long min(long n1, long n2);
51.
Rational& simplify(void);
52.
}; // end of class Rational
53.
} // end of namespace Math
54.
} // end of namespace CSE224
55.
56.
#endif
20-11-2014 13:08
12 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Implementation (Rational)
Rational.cxx
1.
#include <iostream>
2.
#include <memory>
3.
using namespace std;
4.
5.
#include "Object"
6.
using namespace System;
7.
8.
#include "math/exceptions/NoInverse"
9.
#include "math/exceptions/ZeroDenominator"
10.
#include "math/exceptions/ZeroDivisor"
11.
using namespace CSE224::Math::Exceptions;
12.
13.
#include "math/Rational"
14.
15.
namespace CSE224 {
16.
namespace Math {
17.
Rational& Rational::
18.
add(const Rational& rhs) const {
Note the missing try-catch block! Unlike Java, C++ does not mandate the programmer to put all potentially problematic code
in a guarded region. One can say all C++ exceptions are treated like the Java exceptions deriving from the
RuntimeException class. This gives the programmer a degree of freedom that enables her to come up with cleaner code. For
instance, reaching the next line means we are adding two well-formed Rational objects. Result of such an action can never
create a problem!
19.
Rational* sum = new Rational(_n * rhs._d + _d * rhs._n, _d * rhs._d);
20.
21.
return sum->simplify();
22.
} // end of Rational& Rational::add(const Rational&) const
23.
24.
20-11-2014 13:08
13 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Rational& Rational::
25.
divide(const Rational& rhs) const throw(ZeroDivisor) {
26.
try {
27.
Rational& tmp_inv = rhs.inverse();
28.
Rational& ret_rat = this->multiply(tmp_inv);
Now that we are done with the temporary object that holds the inverse of rhs, we must return it to the memory allocator or put
up with the consequences of creating garbage at each use of this function. Thats pretty annoying! But then again, a C/C++
programmer does not make such easy mistakes.
Notice the address-of operator before tmp_inv. Application of this operator to a reference returns the starting address of the
region aliased by the reference. [Remember references are silently dereferenced at their point of use] In our case, this will be the
address of the object created as a result of sending the message inverse to rhs.
28.
delete &tmp_inv;
29.
return ret_rat;
30.
} catch (NoInverse e) {
31.
cerr << "Error: About to throw ZeroDivisor exception" << endl;
32.
throw ZeroDivisor();
33.
}
34.
} // end of Rational& Rational::divide(const Rational&) const
35.
36.
Rational& Rational::
37.
inverse(void) const throw(NoInverse) {
38.
try {
39.
Rational *res = new Rational(_d, _n);
40.
return *res;
41.
} catch(ZeroDenominator e) {
42.
cerr << "Error: About to throw NoInverse exception" << endl;
43.
throw NoInverse(_n, _d);
44.
}
45.
} // end of Rational& Rational::inverse(void) const
46.
47.
Rational& Rational::
48.
multiply(const Rational& rhs) const {
20-11-2014 13:08
14 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
49.
Rational *res = new Rational(_n * rhs._n, _d * rhs._d);
50.
51.
return res->simplify();
52.
} // end of Rational& Rational::multiply(const Rational&) const
53.
54.
Rational& Rational::
55.
subtract(const Rational& rhs) const {
We formulate subtraction in terms of other operations: instead of subtracting a value, we add the negated value. For doing this
we create two temporary objects meaningful only throughout the current call. Before returning to the caller we should return
them to the memory allocator.
A so-called smart pointer object is exactly what we want. Such an object is initialized to point to a dynamically allocated object
created by a new expression and frees itthe dynamically allocated objectat the end of its (smart pointers) lifetime. The
following figure showing the memory layout after execution of line 47 should make this clear.
Heap object local to the function is created together with the smart pointer object, which is itself a local object created on the
run-time stack.9 This means allocation-constructor call and destructor call-deallocation of this smart pointer object will be
handled by the compiler-synthesized code. In other words, programmer need not worry about the life-cycle management of the
smart pointer object. So, if we can guarantee the heap object is destroyed-deallocated together with this smart pointer, its
life-cycle management will not be a problem anymore. This is accomplished by delet(e)ing the heap object within the destructor
of the related smart pointer object, which means the heap object will have been destroyed-deallocated by the time the
destruction of the smart pointer object is over. The following then describes the life-cycle of the smart pointer and the related
heap object.
1. Create the smart pointer in the run-time stack.
1. Pass the related heap object to the constructor of the smart pointer.
2. Use the heap object.
3. Call the destructor of the smart pointer by means of the compiler-synthesized code.
1. Delete the heap object from within the destructor of the smart pointer.
According to this, anonymous Rational objectsnew Rational(-1) and &(rhs.multiply(*neg_1))created in
the following definitions will have been automaticallythat is, without the intervention of the programmerreturned before
leaving the function.
56.
auto_ptr< Rational > neg_1(new Rational(-1));
57.
auto_ptr< Rational > tmp_mul(&(rhs.multiply(*neg_1)));
20-11-2014 13:08
15 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Observe the following application of the dereferencing operator receives as its operand a non-pointer variable, which may at first
seem as an error. After all, * works by returning the contents of memory indicated by its sole operand. However, this rather
limited description ignores the possibility of overloading the dereferencing operator. It is indeed the overloaded version of this
operator that enables the use of a non-pointer type. The following application of * makes use of the overloaded version defined
within the auto_ptr class, which returns the contents of the heap object managed by the smart pointer.
To make things clearer, we can suggest the following implementation for the auto_ptr class.
template <class ManagedObjectType>
class auto_ptr {
public:
auto_ptr(ManagedObjectType* managedObj) {
_managed_heap_object = managedObj;
...
} // end of constructor(ManagedObjectType*)
...
ManagedObjectType operator*(void) {
...
return *_managed_heap_object;
} // end of ManagedObjectType operator*(void)
...
private:
ManagedObjectType* _managed_heap_object;
} // end of class auto_ptr<ManagedObjectType>
58.
Rational &ret_rat = add(*tmp_mul);
59.
60.
return(ret_rat);
61.
} // end of Rational& Rational::subtract(const Rational&) const
62.
63.
int Rational::
64.
compareTo(const Object& rhs) const {
65.
double this_equi = ((double) _n) / _d;
In addition to the traditional C-style casting C++ offers a variety of cast operators: const_cast, dynamic_cast,
static_cast, and reinterpret_cast. Each of these performs a subset of the functionality offered by the traditional cast
operator and therefore one can say the new operators do not add any new functionality. Nevertheless, thanks to the extra support
from the compiler, they enable writing more type-safe programs. Using the new operators we state our intentions explicitly and
therefore get more maintainable code.
Example: Removing const-ness property of an object.
class B { };
...
20-11-2014 13:08
16 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
const B cb = B;
...
B b = const_cast<B&>(cb); // equivalent to (B) cb or B (cb).
...
It should be noted that const_cast can also be used to change volatile-ness property of an object.
Since our intention of removing the const-ness has been made explicit by the relevant operator, maintainer of the code will
more quickly spot the occurrence of cast and realize what is being done. Alternative scheme of using the C-style cast lacks these
qualities: it is both difficult to find the location of the cast and figure out that const-ness is being removed.
Using dynamic_cast will also provide us with the benefit of safer code. This particular operator is used to bi-directionally cast
between polymorphic classesthat is; classes that has at least one virtual functionthat are related with each other by a public
derivation.
Question
dynamic_cast can be used to convert only between pointer/reference types. Why?
Answer
Polymorphic classes enable behavioral and structural variability. This can only be utilized by means of a pointer/reference,
which acts as a facade between the static world required by the compiler/linker and the variability demanded by a dynamic
world. Therefore, dynamic_cast, used to cast between polymorphic classes, expects its parameter to be a
pointer/reference and returns a pointer/reference.
Definition: Converting from one class to another in the same class hierarchy is referred to as downcasting if the target type is
more specialized. In case the target type is less specialized the act of casting is called upcasting.
Upcasting to a public base class is always successful since the messages listed in the interface of the target type is a subset of the
source type interface. On the other hand, casting from a derived class to one of its non-public base classes leads to a
compile-time error. Similarly, downcasting may give rise to run-time errors since we can potentially send messages that are not
found in the interface of the source type.
Example: Safer code with dynamic_cast.
class PB { virtual void f(void) { } };
class PD : public PB { virtual void g(void) { } };
...
PB* pb; ...; pb = new D;
...
PD* pd = dynamic_cast<PD*>(pb);
if (pd == NULL) { ... }
...
The above code downcasts a PB* variable to PD*, through which one can send the extra message named g. For this example,
this doesn't seem to be a problem. But what if pb was used to point to an object of PB instead of PD? What if it is used to point
to objects of different types as is shown in the following fragment?
if(some_condition) { ... ; pb = new D; ... }
else { ...; pb = new B; ... }
...
PD* pd = dynamic_cast<PD*>(pb);
There is no guarantee that we can send g to the underlying object, which can be of type PB or PD. This guarantee we are seeking
can be provided only if we can check the object type at run-time. And this is exactly what dynamic_cast does: by checking
compatibility of the pointer/referencestatic typewith the objectdynamic typedynamic_cast decides whether the cast
taking place is valid or not. If so a legitimate value is returned. Otherwise, in the case of casting a pointer, a NULL value is
returned, which basically removes any possibility of sending an illegal message; in the case of failing to cast a reference
std::bad_cast exception is thrown. Same actions are taken also when source and target types are not related with
inheritance.
Note this cost due to the run-time check performed by the compiler-synthesized code is never experienced as a result of using
the traditional cast operator. This is because the C-style cast operator makes no use of any run-time information.
20-11-2014 13:08
17 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Observe casting up the class hierarchysince messages received by an object of the derived class is a subset of that of its base
classesdoes not need any run-time checks. This means cost due to dynamic_cast is not rationalized: why should we pay for
making a control, whose result is known to us? Solution offered by C++ is another cast operator that does its job using
compile-time information: static_cast. This operator can be used for performing conversions that are implicitly carried out
by the compiler, performing these implicit conversions in the reverse direction. It can also be used in place of dynamic_cast if
skipping the run-time checks is guaranteed to be safe.
Example:
PD* ppd = new PD;
// Implicit conversion, equivalent to static_cast<PB*>(ppd)
PB* ppb = ppd;
...
PD pd = PD;
// Implicit conversion, equivalent to static_cast<PB>(pd)
PB pb = pd;
...
enum Iterator_Type {ITERATOR_FORWARD, ITERATOR_BACKWARD};
int one = ITERATOR_BACKWARD; // Implicit conversion
int val;
...
// reverse conversion
Iterator_Type itr_type = static_cast<Iterator_Type>(val);
PB* pb = new PB();
// Normally, next line should have been written with dynamic_cast. But if we can make
sure pd does not get a g message it is OK and will save us the run-time checks
performed by dynamic_cast.
PD* pd = static_cast<PD*>(pb);
// pd is initialized with a legitimate pointer
This doesn't fully cover the functionality offered by the traditional cast operator. Conversions between unrelated/incompatible
pointer types are missing, for instance. This missing functionality is covered by the reinterpret_cast operator, which
can also perform conversions between pointer and integral types.
Example:
float f = 1.0;
float* fp = &f;
int* ip = reinterpret_cast<int*>(fp);
cout << hex << *ip << '\t' << dec << *fp << '\t' << *ip << endl;
(*ip) = (*ip) + 8 * 1024 * 1024;
cout << hex << *ip << '\t' << dec << *fp << '\t' << *ip << endl;
It should be kept in mind that this operator makes no checks on the source and target types; it simply bitwise-copies the
contents of the target into the source.
66.
double rhs_equi = ((double) dynamic_cast<const Rational&>(rhs)._n) / dynamic_cast<const Rational&>(rhs)._d;
67.
if (this_equi > rhs_equi) return 1;
68.
else if (this_equi == rhs_equi) return 0;
69.
else return -1;
70.
} // end of int Rational::compareTo(const Object&) const
71.
20-11-2014 13:08
18 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
72.
long Rational::
73.
min(long n1, long n2) { return (n1 > n2 ? n1 : n2); }
74.
75.
Rational& Rational::
76.
simplify(void) {
77.
long upperLimit = min(_n, _d);
78.
79.
for (long i = 2; i <= upperLimit;)
80.
if ((_n % i == 0) && (_d % i == 0)) { _n /= i; _d /= i; }
81.
else i++;
82.
if (_d < 0) { _n *= -1; _d *= -1; }
83.
84.
return(*this);
85.
} // end of Rational& Rational::simplify(void)
86.
87.
ostream& operator<<(ostream& os, const Rational& rat) {
88.
os << rat._n << " ";
89.
if (rat._d != 1) os << "/ " << rat._d;
90.
91.
return os;
92.
} // end of ostream& operator<<(ostream&, const Rational&)
93.
} // end of namespace Math
94.
} // end of CSE224
Interface (Whole)
Whole
1.
#ifndef WHOLE_HXX
2.
#define WHOLE_HXX
3.
4.
#include <iostream>
5.
using namespace std;
20-11-2014 13:08
19 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
6.
7.
#include "math/Rational"
8.
9.
namespace CSE224 {
10.
namespace Math {
11.
class Whole : public Rational {
12.
public:
Remember Whole derives from Rational. Put differentlysince inheritance can be seen as a compiler-managed
compositionall Whole objects have a Rational sub-object as part of their memory layouts. Following notation used in the
member initialization list with no reference to the member being initialized will initialize the Rational sub-object found in the
Whole object being constructed.
13.
Whole(long num) : Rational(num) { }
14.
Whole(void) : Rational(0) { }
15.
Whole(Whole& existingWhole) :
16.
Rational(existingWhole.getNumerator()) { }
17.
Whole& add(const Whole& rhs) const;
18.
virtual Rational& add(const Rational&) const;
19.
}; // end of class Whole
20.
} // end of namespace Math
21.
} // end of namespace CSE224
22.
23.
#endif
Implementation (Whole)
Whole.cxx
1.
#include <iostream>
2.
using namespace std;
3.
4.
#include "math/Rational"
20-11-2014 13:08
20 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
5.
#include "math/Whole"
6.
7.
namespace CSE224 {
8.
namespace Math {
9.
Rational& Whole::
10.
add(const Rational& rhs) const {
11.
cout << "[In Whole::add(Rational)] ";
12.
13.
return (Rational::add(rhs));
14.
} // end of Rational& Whole::add(const Rational&) const
15.
16.
Whole& Whole::
17.
add(const Whole& rhs) const {
18.
cout << "[In Whole::add(Whole)] ";
19.
Whole *res = new Whole(getNumerator() + rhs.getNumerator());
20.
21.
return *res;
22.
} // end of Whole& Whole::add(const Whole&) const
23.
} // end of namespace Math
24.
} // end of namespace CSE224
Exception Classes
NoInverse
1.
#ifndef NOINVERSE_HXX
2.
#define NOINVERSE_HXX
3.
4.
#include <iostream>
5.
using namespace std;
6.
7.
namespace CSE224 {
8.
20-11-2014 13:08
21 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
namespace Math{
9.
namespace Exceptions {
10.
class NoInverse {
11.
public:
12.
NoInverse(long num, long den) {
13.
cerr << "Error: Throwing a NoInverse exception" << endl;
14.
_n = num; _d = den;
15.
} // end of constructor(long, long)
16.
17.
void writeNumber(void) {
18.
cerr << "The problematic number is " << _n << "/" << _d << endl;
19.
} // end of void writeNumber()
20.
21.
private:
22.
long _n, _d;
23.
}; // end of class NoInverse
24.
} // end of namespace Exceptions
25.
} // end of namespace Math
26.
} // end of namespace CSE224
27.
28.
#endif
ZeroDenominator
1.
#ifndef ZERODENOMINATOR_HXX
2.
#define ZERODENOMINATOR_HXX
3.
4.
namespace CSE224 {
5.
namespace Math {
6.
namespace Exceptions {
7.
class ZeroDenominator {
8.
public:
9.
ZeroDenominator(void) {
20-11-2014 13:08
22 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
10.
cerr << "Error: Throwing a ZeroDenominator exception" << endl;
11.
} // end of default constructor
12.
}; // end of class ZeroDenominator
13.
} // end of namespace Exceptions
14.
} // end of namespace Math
15.
} // end of namespace CSE224
16.
17.
#endif
ZeroDivisor
1.
#ifndef ZERODIVISOR_HXX
2.
#define ZERODIVISOR_HXX
3.
4.
#include <iostream>
5.
using namespace std;
6.
7.
namespace CSE224 {
8.
namespace Math {
9.
namespace Exceptions {
10.
class ZeroDivisor {
11.
public:
12.
ZeroDivisor(void) {
13.
cerr << "Error: Throwing a ZeroDivisor exception" << endl;
14.
} // end of default constructor
15.
}; // end of class ZeroDivisor
16.
} // end of namespace Exceptions
17.
} // end of namespace Math
18.
} // end of namespace CSE224
19.
20.
#endif
20-11-2014 13:08
23 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Test Program
Test_Whole.cxx
1.
#include <iostream>
2.
#include <memory>
3.
using namespace std;
4.
5.
#include "math/Whole"
6.
using namespace CSE224::Math;
7.
8.
#include "math/exceptions/ZeroDenominator"
9.
using namespace CSE224::Math::Exceptions;
10.
11.
int main(void) {
12.
cout << "Creating a Whole object(five) and initializing it with 5..." << endl;
13.
auto_ptr < Whole > fivep(new Whole(5));
14.
Whole& five = *fivep;
15.
cout << "Creating a Rational object(three) and initializing it with 3..." << endl;
16.
auto_ptr < Rational > threep(new Rational(3));
17.
Rational& three = *threep;
18.
19.
cout << "Result of five.multiply(three) = ";
20.
cout << five.multiply(three) << endl;
21.
22.
cout << "***************************************************" << endl;
23.
24.
cout << "Result of three.add(three) = ";
25.
cout << three.add(three) << endl;
26.
27.
cout << "Result of three.add(five) = ";
28.
cout << three.add(five) << endl;
29.
30.
cout << "Result of five.add(three) = ";
20-11-2014 13:08
24 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
31.
cout << five.add(three) << endl;
32.
33.
cout << "Result of five.add(five) = ";
34.
cout << five.add(five) << endl;
35.
36.
cout << "***************************************************" << endl;
37.
38.
cout << "Setting a Rational object(ratFive) as an alias for a Whole object(five)..." << endl;
39.
Rational& ratFive = five;
40.
41.
cout << "Result of ratFive.add(three) = ";
42.
cout << ratFive.add(three) << endl;
43.
44.
cout << "Result of ratFive.add(five) = ";
45.
cout << ratFive.add(five) << endl;
46.
47.
cout << "Result of ratFive.add(ratFive) = ";
48.
cout << ratFive.add(ratFive) << endl;
49.
50.
cout << "Result of five.add(ratFive) = ";
51.
cout << five.add(ratFive) << endl;
52.
53.
cout << "Result of three.add(ratFive) = ";
54.
cout << three.add(ratFive) << endl;
55.
56.
cout << "***************************************************" << endl;
57.
58.
cout << "Creating a Rational object(r1) and initializing it with 3/2..." << endl;
59.
auto_ptr < Rational > r1p(new Rational(3, 2));
60.
Rational& r1 = *r1p;
61.
cout << "Result of five.multiply(r1) = ";
62.
cout << five.multiply(r1) << endl;
63.
20-11-2014 13:08
25 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
66.
return 0;
67.
} // end of int main(void)
Private Inheritance
Programming being a pragmatic endeavor, one must strive to do it as efficiently as possible. Arguably the most effective way to
achieve this is to re-use artifacts that have already been used in different stages of previous projects.[3] By doing so we save on
development and test time, which means our next product makes it to the market in a shorter time.
One way to achieve reuse is inheritance. And for many it seems to be the only affordable one. Nevertheless, there is a
contender: composition. This technique is realized by making an object a member of another.
class C {... B b; ...};
For the above example we say an object of C is composed of, apart from other things, an object of B. Put differently, we say an
object of C has-an (contains) object of B. This is certainly different than inheritance where the relationship is defined to be an
is-a relationship.
In C++, similar effect can be achieved through the so-called private inheritance.
class C : /* private */ B { ... }
Interface (List)
List
1.
#ifndef LIST_HXX
2.
#define LIST_HXX
3.
4.
#include "ds/exceptions/List_Exceptions"
5.
using namespace CSE224::DS::Exceptions;
6.
7.
namespace CSE224 {
8.
namespace DS {
9.
class List {
10.
friend ostream& operator<<(ostream&, const List&);
11.
public:
12.
List(void) : _size(0), _head(NULL), _tail(NULL) { }
13.
List(const List&);
14.
20-11-2014 13:08
26 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
~List(void);
15.
List& operator=(const List&);
16.
bool operator==(const List&);
17.
18.
double get_first(void) throw(List_Empty);
19.
double get_last(void) throw(List_Empty);
20.
void insert_at_end(double new_item);
21.
void insert_in_front(double new_item);
22.
double remove_from_end(void) throw(List_Empty);
23.
double remove_from_front(void) throw(List_Empty);
24.
bool is_empty(void);
25.
unsigned int size(void);
26.
27.
private:
What follows is the definition of a nested class, a class defined inside another. Such a class, when defined in a private
section, is not visible outside its enclosing class. This scheme is useful when the two classes are tightly coupled, as is the case in
our example: A List_Node object is used only in the context of a List object. What makes up our List objects is an
implementation detail and should not be a concern to their users.
Although it might be tempting to draw a parallel between nested classes and inner classes of Java, that would be a mistake. As
opposed to the special relation between the inner class and its enclosing class, in C++ the enclosing class has no special access
privileges with regard to the classes nested within it. For this reason, changing public to private or protected is not a
good idea.
Another remark to be made is that nested classes of C++ do not keep any record of the enclosing object in the objects of the
inner class, which makes them like more the static inner classes of Java.
28.
class List_Node {
29.
public:
30.
List_Node(double val) : _info(val), _next(NULL), _prev(NULL) { }
31.
32.
double _info;
33.
List_Node *_next, *_prev;
34.
}; // end of class List_Node
35.
private:
36.
List_Node *_head, *_tail;
37.
20-11-2014 13:08
27 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
A function declared in the private section? Yes! Functions, which are used by other functions and are not part of the
interface, are declared in the private section. Note that you cannot get away without declaring such functions in the class
interface.
38.
void insert_first_node(double);
39.
}; // end of List class
40.
} // end of namespace DS
41.
} // end of namespace CSE224
42.
43.
#endif
Implementation (List)
List.cxx
1.
#include <iostream>
2.
using namespace std;
3.
4.
#include "ds/List"
5.
#include "ds/exceptions/List_Exceptions"
6.
using namespace CSE224::DS::Exceptions;
7.
8.
namespace CSE224 {
9.
namespace DS {
10.
List::
11.
List(const List& rhs) : _head(NULL), _tail(NULL), _size(0) {
12.
List_Node *ptr = rhs._head;
13.
for(unsigned int i = 0; i < rhs._size; i++) {
14.
this->insert_at_end(ptr->_info);
15.
ptr = ptr->_next;
16.
} // end of for(unsigned int i = 0; i < rhs._size; i++)
20-11-2014 13:08
28 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
17.
} // end of copy constructor
Now that nodes of the List objects are allocated on the heap, we must make sure they are turned back over to the memory
allocator as the List object itself is, implicitly or explicitly, deleted. For this reason, we need to write a destructor to free these
nodes. Note a List object is made up of two pointers, which show the head and the tail of the list, and a field holding its size.
The nodes pointed, directly or indirectly, by these pointers are not part of the List object. So, they will not be automatically
freed together with the list object. For this reason, we do need a destructor.
Notice our decision is not affected by whether the List object itself is created on the heap or not. Where the List object is
created has an effect on who should call the destructor: Whoever the responsible party might be, compiler or programmer, in all
possible scenarios the destructor is implicitly called before deallocation of the object. If it is created on the heap the programmer
is responsible for making the call. Otherwise, the compiler will take care of the drudgery as the scope of the object is closed.
19.
List::
20.
~List(void) {
21.
for (int i = 0; i < _size; i++)
22.
remove_from_front();
23.
} // end of destructor
24.
25.
List& List::
26.
operator=(const List& rhs) {
27.
if (this == &rhs) return (*this);
28.
29.
for(unsigned int i = _size; i > 0; i--)
30.
this->remove_from_front();
31.
List_Node *ptr = rhs._head;
32.
for(unsigned int i = 0; i < rhs._size; i++) {
33.
this->insert_at_end(ptr->_info);
34.
ptr = ptr->_next;
35.
} // end of for(unsigned int i = 0; i < rhs._size; i++)
36.
37.
if (rhs._size == 0) {
38.
_head = _tail = NULL;
39.
_size = 0;
40.
} // end of if(rhs._size == 0)
41.
20-11-2014 13:08
29 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
42.
return (*this);
43.
} // end of assignment operator
44.
45.
bool List::
46.
operator==(const List& rhs) {
47.
if (_size != rhs._size) return false;
48.
if (_size == 0 || this == &rhs) return true;
49.
50.
List_Node *ptr = _head;
51.
List_Node *ptr_rhs = rhs._head;
52.
for (unsigned int i = 0; i < _size; i++) {
53.
if (!(ptr->_info == ptr_rhs->_info))
54.
return false;
55.
ptr = ptr->_next;
56.
ptr_rhs = ptr_rhs->_next;
57.
} // end of for(unsigned int i = 0; i < _size; i++)
58.
59.
return true;
60.
} // end of equality-test operator
61.
62.
double List::
63.
get_first(void) throw(List_Empty) {
64.
if (is_empty()) throw List_Empty();
65.
66.
return (_head->_info);
67.
} // end of double List::get_first(void)
68.
69.
double List::
70.
get_last(void) throw(List_Empty) {
71.
if (is_empty()) throw List_Empty();
72.
73.
return (_tail->_info);
74.
} // end of double List::get_last(void)
20-11-2014 13:08
30 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
75.
76.
void List::
77.
insert_at_end(double new_item) {
78.
if (is_empty()) insert_first_node(new_item);
79.
else {
80.
List_Node *new_node = new List_Node(new_item);
81.
_tail->_next = new_node;
82.
new_node->_prev = _tail;
83.
_tail = new_node;
84.
}
85.
_size++;
86.
} // end of void List::insert_at_end(double)
87.
88.
void List::
89.
insert_in_front(double new_item) {
90.
if (is_empty()) insert_first_node(new_item);
91.
else {
92.
List_Node *new_node = new List_Node(new_item);
93.
new_node->_next = _head;
94.
_head->_prev = new_node;
95.
_head = new_node;
96.
}
97.
_size++;
98.
} // end of void List::insert_in_front(double)
99.
100.
double List::
101.
remove_from_end(void) throw(List_Empty) {
102.
if (is_empty()) throw List_Empty();
103.
104.
double ret_val = _tail->_info;
105.
List_Node *temp_node = _tail;
106.
107.
20-11-2014 13:08
31 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
113.
delete temp_node;
114.
_size--;
115.
116.
return ret_val;
117.
} // end of double List::remove_from_front(void)
118.
119.
double List::
120.
remove_from_front(void) throw(List_Empty) {
121.
if (is_empty()) throw List_Empty();
122.
123.
double ret_val = _head->_info;
124.
List_Node *temp_node = _head;
125.
126.
if (_size == 1) { _head = _tail = NULL; }
127.
else {
128.
_head = _head->_next;
129.
_head->_prev = NULL;
130.
}
131.
132.
delete temp_node;
133.
_size--;
134.
135.
return ret_val;
136.
} // end of double List::remove_from_front(void)
137.
138.
bool List::
139.
is_empty(void) { return(_size == 0); }
20-11-2014 13:08
32 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
140.
141.
unsigned int List::
142.
size(void) { return _size; }
143.
144.
void List::
145.
insert_first_node(double new_item) {
146.
List_Node *new_node = new List_Node(new_item);
147.
_head = _tail = new_node;
148.
} // end of void List::insert_first_node(double)
149.
150.
ostream& operator<<(ostream& os, const List& rhs) {
151.
List tmp_list = rhs;
152.
153.
os << "<" << rhs._size << "> <head: ";
154.
for (int i = 0; i < rhs._size - 1; i++) {
155.
os << tmp_list._head->_info << ", ";
156.
tmp_list._head = tmp_list._head->_next;
157.
} // end of for(int i = 0; i < rhs._size; i++)
158.
if (rhs._size > 0) os << tmp_list._head->_info;
159.
os << ": tail>";
160.
161.
return(os);
162.
} // end of ostream& operator<<(ostream&, const List&)
163.
} // end of namespace DS
164.
} // end of namespace CSE224
Interface (Stack)
Stack
1.
#ifndef STACK_HXX
2.
#define STACK_HXX
3.
20-11-2014 13:08
33 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
4.
#include <iostream>
5.
using namespace std;
6.
7.
#include "ds/exceptions/Stack_Exceptions"
8.
using namespace CSE224::DS::Exceptions;
9.
10.
#include "ds/List"
11.
12.
namespace CSE224 {
13.
namespace DS {
The List class offers a superset of the functionality expected from a stack. This may at first lead us to think that we may
define a new class, Stack, and have it (publicly) derive from the List class. The problem with this approach is that the public
interface of the base class will be exposed as part of the public interface of the derived class. Not really what we would want in
this case: the List class offers a lot more than what we would expect from the Stack class. For this reason we should resort
to some other method, such as composition.
C++ offers an alternative: private inheritance. Using private inheritance, the derived class can still make use of the
functionality offered by the base class but the base class interface is not exposed through the derived class. For this reason,
Stack class privately inherits from the List class.
14.
class Stack : private List {
15.
public:
Now that the derived class can reuse the base class functionality but do not expose it to its users, this type of inheritance is also
called implementation inheritance. For a similar reason, public inheritance is also called interface inheritance.
We do not need to write the functions of the orthodox canonical form because compiler-synthesized versions provide the
equivalent of what we are required to do. This is basically because the only data field of Stack is the List sub-object it
inherits from List.
16.
// Stack(void);
17.
// Stack(const Stack&);
18.
// ~Stack(void);
19.
// Stack& operator=(const Stack&);
20.
// bool operator==(const Stack&);
21.
20-11-2014 13:08
34 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
22.
double peek(void) throw(Stack_Empty);
23.
double pop(void) throw(Stack_Empty);
24.
void push(double new_item);
Thanks to the following statement, we selectively expose a function from the privately inherited base class. Its as if the
is_empty function(s) from the List class were publicly inherited.
26.
using List::is_empty;
27.
}; // end of Stack class
28.
} // end of namespace DS
29.
} // end of namespace CSE224
30.
31.
#endif
Implementation (Stack)
Stack.cxx
1.
#include <iostream>
2.
using namespace std;
3.
4.
#include "ds/Stack"
5.
#include "ds/exceptions/Stack_Exceptions"
6.
using namespace CSE224::DS::Exceptions;
7.
8.
namespace CSE224 {
9.
namespace DS {
10.
double Stack::
11.
peek(void) throw(Stack_Empty) {
12.
double ret_val;
20-11-2014 13:08
35 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Users of our class should not be aware of how we implement the Stack class. Thats why we need to re-throw the exceptions
thrown by the List class so that it makes more sense to the user.
14.
try { ret_val = get_first();}
15.
catch(List_Empty) { throw Stack_Empty(); }
16.
17.
return ret_val;
18.
} // end of double Stack::peek(void)
19.
20.
void Stack::
21.
push(double new_item) { List::insert_in_front(new_item); }
22.
23.
double Stack::
24.
pop(void) throw(Stack_Empty) {
25.
double ret_val;
26.
27.
try { ret_val = remove_from_front(); }
28.
catch(List_Empty) { throw Stack_Empty(); }
29.
30.
return ret_val;
31.
} // end of double Stack::pop(void)
32.
} // end of namespace DS
33.
} // end of namespace CSE224
Virtual Inheritance
With the possibility of multiple inheritance rises the issue of the so-called virtual inheritance. Consider the class hierarchy
shown in Figure 2. Question that awaits your answer is: How many Animal sub-objects will there be in a Politician
object? Looking at the figure the correct answer seems to be two. However, our logic tells us a different story: there can be only
one Animal sub-object in a Politician.
20-11-2014 13:08
36 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Whichever one is the right answer, there might be cases where either one turns out to be a better choice. We must find a way to
tell the difference between the options. This is where the notion of virtual inheritance comes into the picture. We define the
Humanoid and Ape classes to be virtually derived from Animal.
Example: Virtual inheritance
class Animal { ... };
class Humanoid : public virtual Animal { ... };
class Ape : public virtual Animal { ... };
class Politician : public Humanoid, public Ape { ... };
Thanks to these definitions, there is now only one Animal sub-object in a Politician. This is achieved by assuring that
pointersnot objects themselvesare inserted into the derived classes. That is; instead of containing two Animal sub-objects,
a Politician object now has two pointers both pointing to the same Animal sub-object.
Note use of virtual inheritance causes the order of constructor call to be changed: Virtual base classes are always constructed
prior to non-virtual base classes regardless of where they appear in the inheritance hierarchy.
A typical use of virtual inheritance involves implementation of mix-in classes. Mix-in classes are used for tuning the behavior of
a base class and can be combined to obtain even more specialized classes. For example, using the following code one can create
windows with different styles: plain window, window with menu, window with border, and window with menu and border. As a
matter of fact, we can come up with our own mix-in, say scroll-bar mix-in, and get scroll-bar-supporting versions of these
window styles.
Example: Implementing mix-ins by virtual inheritance.
class Plain_Window {
public:
/* Basic window functions common to all windows */
private:
...
}; // end of class Plain_Window
class Menu_Mixin : public virtual Plain_Window {
public:
/* Functions specially required for manipulating window menus. */
private:
...
}; // end of class Menu_Mixin
class Border_Mixin : public virtual Plain_Window {
public:
/* Functions specially required for manipulating window borders. */
private:
...
20-11-2014 13:08
37 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Implementing Polymorphism
In this section, we take a look at two widely used methods of implementing polymorphism. It is worth noting that both rely on
dynamically dispatching the function call to the relevant [function] entry point. In other words, polymorphism is implemented
by means of dynamic dispatch. Another point to make is the tool we use for expressing polymorphism: inheritance.
Mentioning polymorphism, inheritance, and dynamic dispatch in different contexts may make some think of them as unrelated
concepts. This, however, is utterly wrong. Truth of the matter is, in an object-oriented context these are complementary
concepts and cooperate for enabling the implementation of the is-a relation in a natural way. In order to express an is-a relation
between two classes we need help from both inheritance and polymorphism: inheritance is used to define a common message
protocol between the base and derived classes, whereas polymorphism is needed for providing the behavioral variability.[4]
This is further made possible by dynamically dispatching the messages.
As was mentioned in previous section, inheritance without polymorphism leads to inflexible, object-based solutions. Likewise,
polymorphism alone is generally not what you want. So, it is well-advised that you consider using these two together.
Previous remarks should not make you think we are bound to use the duo of inheritance and polymorphism only. Our success
in software industry depends on producing reliable, easily-extensible, efficient software. The keyword in reaching this goal is
reuse and aforementioned concepts are not without alternatives. Apart from the age-old composition technique, we can use
generics for instance. Parameterizing a class or a subprogram will also give us the benefits of reuse. An example to the former
is given in the Parameterized Types chapter, while use of the latter is provided below. Thanks to this method, users can sort any
array provided that a Comparator<V> object for the component type is also supplied.
Example: Generic methods in Java.
public class BubbleSort {
public static <V> void sort(V[] arr, Comparator<? super V> c) {
for (int i = arr. length - 1; i > 0; i--) {
int noOfSwaps = 0;
for (int j = 0; j < i; j++)
if (c. compare(arr[j], arr[j + 1]) > 0) {
CommonMethods.swap(arr, j, j + 1);
noOfSwaps++;
}
} // end of outer for-loop
} // end of <V> void sort(V[], Comparator<? super V>)
} // end of class BubbleSort
Such a method is said to provide parametric polymorphism. It is polymorphic in the sense that same method does the same
thing for parameters of different types; to the user of the method, it looks as though there were separate methods for each
different type.[5]
A similar effect can be experienced in the case of overloaded subprograms, where calls with different argument lists are
dispatched to subprograms with different signatures and users get different behavior as a result of calling the seemingly same
subprogram. For this reason, overloading is sometimes referred to as ad-hoc polymorphism.[6]
Having done away with the confusion about inheritance and polymorphism, let's move on to the techniques commonly used in
20-11-2014 13:08
38 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
implementing polymorphism. But before we do that, we will give a listing of the sample classes used in our presentation.
class B1 {
...
public:
virtual void f(int);
void fs(int);
...
...
} ; // end of base class B1
class B2 {
...
public:
virtual void f(int);
virtual void g(void);
...
...
} ; // end of base class B2
class D : public B1, public B2 {
...
public:
void f(int);
...
...
}; // end of derived class D
vtables
The vtable technique, generally used in compilers of the PC world, utilizes a table containing rows of function entry address
and offset pair. Objects of all classes with at least one dynamically dispatched function has a pointer, called the vptr, pointing
to the start of this table. The offset column is used for adjusting the value of the this pointer. This adjustment is required
when an object of a derived class is used through a pointer of a base class. Take the definition of D, whose object memory
layout is given below. Given an object of D, we can use it through pointers of D and any type that is an ancestor type of D,
which in our case are D*, B1*, and B2*. Put another way, through a variable of type B1* we can manipulate any object that is
an instance of a class deriving from B1, which in our case are D and B1. Accordingly, the following is possible.
...
D* d_obj = new D(...);
B1* b1_obj = new B1(...);
B2* b2_obj = new B2(...);
... fd(D* d_par) {
...
d_par->f(...);
d_par->g(...);
...
} // end of ... fd(D*)
... fb1(B1* b1_par) {
...
b1_par->f(...);
...
...
} // end of ... fb1(B1*)
... fb2(B2* b2_par) {
...
b2_par->f(...);
b2_par->g(...);
...
20-11-2014 13:08
39 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
fd(d_obj) ...;
fb1(b1_obj) ...;
fb1(d_obj) ...;
fb2(b2_obj) ...;
fb2(d_obj) ...;
Having overridden f in the derived class (D) means invoking this version of f will potentially make use of all properties found
in B1, B2, and D, which implies the receiver object of this function must at least be a D object. As was mentioned before such
an object can also be manipulated through B2*. This is exemplified by executing line 7 of the above fragment following the
call on line 15. Note also, upon executing the function call on line 14, the function call on line 7 is dispatched with a B2 object.
Since b2_par is of type B2*, both cases are handled via the vptr field of a B2 object. However, in one case this object is B2
part of a D object whereas in the other it is a plain B2 object. This is shown in figures above.
B2 as part of a D object needs our attention. Starting address of the object (D) and the pointer used for manipulating this object
(B2*) indicate different locations in memory. This requires thatin order to enable use of all properties of a D objectwe
adjust the pointer value as many bytes as there are before the B2 sub-object, which is referred to as delta(B2) in our figure.
Adjustor Thunks
The second technique we will look at can be seen as a less portable [7] optimization on the first one. As in the vptr technique,
one column in the vtable contains a pointer-to-function. But we now utilize thunks instead of the adjustment column. In case
an adjustment to this is needed, pointer-to-function in the one and only column of the vtable points to the thunk code generated
by the compiler, which modifies this and jumps to the function entry. If no adjustment to this is required, the pointerto-function contains the address of the entry of the function to be invoked.
Insert Adjustor Thunks figure here
20-11-2014 13:08
40 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
void f(void) {
cout << "First statement in f..." << endl;
static C c_sl;
cout << "Last statement in f..." << endl;
} // end of void f(void)
C c_g;
int main(void) {
f();
cout << "In main..." << endl;
f();
return 0;
} // end of int main(void)
In case there may not be any programmer-defined constructors, the C++ compiler synthesizes a default constructor that makes
calls to default constructors of the sub-objects. In an object with primitive type fieldssince primitive types do not support the
notion of constructorthis means no constructor call is ever made, which leaves the newly created object in a random state.
Complementing construction, destruction of the object is accomplished by a call to the destructor function, which is followed
by release of the object's memory. The destructor, if there is any, is meant to release resources- both heap memory and outside
resources- being used by the soon-to-be recycled object.
Destructor invocations of static variables take place following the last statement of the program while block variables are
destroyed upon exit from the block they are defined in.[8] In the presence of multiple variable declarations in the same scope,
the order of destructor calls for both kinds of variables is the reverse of the constructor call order.
20-11-2014 13:08
41 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
delete o3;
}
...
...
In the
In the
In the
In the
In the
In the
In the
In the
...
//
//
//
//
//
//
//
//
for
for
for
for
for
for
for
for
o3
o
o1
o2
o3
o2
o1
o
...
In the
In the
In the
In the
...
default constructor of C1
default constructor of C2
destructor of C2
destructor of C1
//
//
//
//
for
for
for
for
o._o
o
o
o._o
In the case of multiple sub-objects constructor calls are made in the order in which they occur in the class definition.
Example: An object with multiple sub-objects.
class C3 {
public:
C3(void) { cout << "In the default constructor of C3" << endl; }
~C3(void) { cout << "In the destructor of C3" << endl; }
private:
C1 _o1, _o2;
}; // end of class C3
...
{
C3 o;
...;
20-11-2014 13:08
42 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
}
...
...
In the
In the
In the
In the
In the
In the
...
default constructor of C1
default constructor of C1
default constructor of C3
destructor of C3
destructor of C1
destructor of C1
//
//
//
//
//
//
for
for
for
for
for
for
o._o1
o._o2
o
o
o._o2
o._o1
}
...
...
In the
In the
In the
In the
In the
In the
In the
In the
...
default constructor
default constructor
default constructor
default constructor
destructor of C2
destructor of C1
destructor of C2
destructor of C1
of
of
of
of
C1
C2
C1
C2
//
//
//
//
//
//
//
//
for
for
for
for
for
for
for
for
o[0]._o
o[0]
o[1]._o
o[1]
o[1]
o[1]._o
o[0]
o[0]._o
20-11-2014 13:08
43 of 48
...
In the
In the
In the
In the
In the
In the
...
default constructor of C1
default constructor of C1
default constructor of C4
destructor of C4
destructor of C1
destructor of C1
//
//
//
//
//
//
for
for
for
for
for
for
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
o._o[0]
o._o[1]
o
o
o._o[1]
o._o[0]
Example:
class C5 {
public:
C5(void) { cout << "In the default constructor of C5" << endl; }
~C5(void) { cout << "In the destructor of C5" << endl; }
private:
C2 _o[2];
}; // end of class C5
...
{
C5 o;
...;
}
...
...
In the
In the
In the
In the
In the
In the
In the
In the
In the
In the
...
default constructor
default constructor
default constructor
default constructor
default constructor
destructor of C5
destructor of C2
destructor of C1
destructor of C2
destructor of C1
of
of
of
of
of
C1
C2
C1
C2
C5
//
//
//
//
//
//
//
//
//
//
for
for
for
for
for
for
for
for
for
for
o._o[0]._o
o._o[0]
o._o[1]._o
o._o[1]
o
o
o._o[1]
o._o[1]._o
o._o[0]
o._o[0]._o
Inheritance
Seeing inheritance as "compiler-managed composition" is key to figuring out constructor and destructor call order. Rest is all
the same.
Example:
class IC1 : public C1 {
public:
IC1(void) { cout << "In the default constructor of IC1" << endl; }
~IC1(void) { cout << "In the destructor of IC1" << endl; }
}; // end of class IC1
...
{
IC1 o1;
...;
}
...
First line of the above fragment can be thought of having been converted by the compiler to the following. Note the identifier
name is arbitrary and cannot be in any way referenced in the class implementation.
class IC1 {
20-11-2014 13:08
44 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
public:
C1 _c1_part;
private:
...
In the
In the
In the
In the
...
default constructor of C1
default constructor of IC1
destructor of IC1
destructor of C1
//
//
//
//
for
for
for
for
C1 part of o1
o1
o1
C1 part of o1
...
In the
In the
In the
In the
In the
In the
...
default constructor of C1
default constructor of C1
default constructor of IC2
destructor of IC2
destructor of C1
destructor of C1
//
//
//
//
//
//
for
for
for
for
for
for
C1 part of o1
o1._o
o1
o1
o1._o
C1 part of o1
20-11-2014 13:08
45 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
For pedagogical purposes, line 4 of the above fragment can be seen as the following. However, since it replaces two sequences
of "an initialization followed by an assignment" with two initializations, using a member initialization list is a more efficient
choice. This is becauseeven when you don't have a member initialization listconstructor(s) of the sub-objects are called
before that of the composite object, which means the two lines in the following fragment are actually assignments, not
initializations.[9] Before> they get executed each sub-object will have already been initialized using the default constructor of
C1.
IC3(int i) {
_c1_part = C1(1);
_o = C1(1);
...
In the
In the
In the
In the
In the
In the
...
//
//
//
//
//
//
for
for
for
for
for
for
C1 part of o1
o1._o
o1
o1
o1._o
C1 part of o1
Multiple Inheritance
Building on our informal definition of inheritance as compiler-managed composition we can treat objects of multiply inheriting
classes as being composed of more than one sub-object. Therefore, for pedagogical purposes, we can accordingly consider line
1 of the following fragment as below.
class IC4 {
public:
C1 _c1_part;
C2 _c2_part;
Example:
1.
class IC4 : public C1, public C2 {
2.
public:
3.
IC4(void) { cout << "In the default constructor of IC4" << endl; }
4.
~IC4(void) { cout << "In the destructor of IC4" << endl; }
5.
}; // end of class IC4
6.
7.
...
8.
{
9.
IC4 o1;
10.
...;
11.
}
12.
...
20-11-2014 13:08
46 of 48
...
In the
In the
In the
In the
In the
In the
In the
In the
...
default constructor
default constructor
default constructor
default constructor
destructor of IC4
destructor of C2
destructor of C1
destructor of C1
of
of
of
of
C1
C1
C2
IC4
//
//
//
//
//
//
//
//
for
for
for
for
for
for
for
for
C1
_o
C2
o1
o1
C2
_o
C1
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
part of o1
of the C2 part of o1
part of o1
part of o1
of the C2 part of o1
part of o1
class CC1 {
public: // public!!!
SC1 _o; // SC1 part
private:
...;
public:
...;
void SC1_f1(...) { _o.SC1_f1(...); }
void SC1_f2(...) { _o.SC1_f2(...); }
... CC1_f1(...);
... CC1_f2(...);
...;
}; // end of class CC1
Private derivation means the functionality in the base class is not visible through an object of the derived class. However, it is
still possible to utilize this functionality in implementing the functions found in the interface of the derived class.
class CC2 {
SC1 _o;
...;
public:
...;
... CC2_f1(...);
... CC2_f2(...);
... CC2_SC1_f1(...) {
...; _o.SC1_f1(...); ...;
} // end of ... CC2_SC1_f1(...)
... CC2_SC1_f2(...) {
20-11-2014 13:08
47 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
Sometimes a mixture of the two cases may be needed. That is; part of the functionality in the base class is visible and rest has
to to be hidden. This selective exposition can be accomplished by a combination of private inheritance and the using
declaration.
class CC3 {
SC1 _o;
...;
public:
void CC3_SC1_f2(...) {
_o.SC1_f2(...);
} // end of void CC3_SC1_f2(...)
...;
... CC3_f1(...);
... CC3_f2(...);
... CC3_SC1_f1(...) {
...; _o.SC1_f1(...); ...;
} // end of ... CC3_SC1_f1(...)
...;
}; // end of class CC3
Virtual Inheritance
Object of a virtual base class is always constructed prior to the objects of non-virtual classes. It should be kept in mind that
"virtualness" is actually the property of the derivation, not that of the base class itself.
Example:
class VC2 : public virtual C1 {
public:
VC2(void) { cout << "In the default constructor of VC2" << endl; }
~VC2(void) { cout << "In the destructor of VC2" << endl; }
}; // end of class VC2
...
{
VC2 o;
...;
}
...
...
In the
In the
In the
In the
...
default constructor of C1
default constructor of VC2
destructor of VC2
destructor of C1
Example:
class VC4 : public VC2, public VC3 {
public:
VC2(void) { cout << "In the default constructor of VC2" << endl; }
~VC2(void) { cout << "In the destructor of VC2" << endl; }
}; // end of class VC2
20-11-2014 13:08
48 of 48
http://en.wikibooks.org/wiki/Programming_Language_Concepts_Using...
...
{
VC4 o;
...;
}
...
...
In the
In the
In the
In the
In the
In the
In the
In the
...
default constructor
default constructor
default constructor
default constructor
destructor of VC4
destructor of VC3
destructor of VC2
destructor of C1
of
of
of
of
C1
VC2
VC3
VC4
Notes
1. One other option would be to make use of the code generation aspect of Java annotations.
2. The only thing that lacks in this picture is garbage-collection. A partial solution to this will be provided by means of
smart pointers, which will be introduced in the implementation part.
3. Reuse is usually taken to be code reuse. However, analysis and design documents, program code, test cases and test
code are all candidates for reuse. As a matter of fact, reuse of artifacts from the early stages of the software production
process has a larger impact on productivity.
4. We can achieve the same result by defining the common message protocol in an interface and get classes to implement
this interface.
5. Notice it is once again dynamic dispatch that makes the magic work. It is not known which method the compare
message will be dispatched to until the program is run and line 6 is executed. Use of pointer-to-function in the equivalent
C code of Callback section in the Sample C Programs chapter is a testimony to this.
6. Unlike other contexts where the word "polymorphism" passes, the function callby matching the arguments with the
signature of the functionis resolved at compile- or link-time and therefore statically dispatched.
7. For instance, some architectures do not allow the goto instruction into the body of another function.
8. Destructor invocations of dynamically allocated objects take place at the point of the relevant application of delete.
9. This belies our definition of a constructor, which states that a constructor is used to initialize an object. As far as C++
is concerned, what takes place in a constructor is assignment; initialization is made using the member initialization list.
20-11-2014 13:08