the quarks of object-oriented development deborah j. armstrong, cacm 49(2006), vol.2 p123-128...
TRANSCRIPT
The Quarks of Object-Oriented DevelopmentDeborah J. Armstrong, CACM 49(2006), vol.2 p123-128
Construct Concept Definition
Structure Abstraction creating classes to simplify aspects of reality using distinctions inherent to the problem.
Class a description of the organization and the actions shared by one or more similar objects.
Encapsulation restricts access to the data and behavior by defining a limited set of messages an object can receive.
Inheritance allows the data and behavior of one class to be used as the basis for another class.
Object an individual, identifiable item which contains data about itself and descriptions of its manipulations of the data.
Behavior Message Passing the process by which an object sends data to another object or asks the other object to invoke an action.
Method a way to access, set, or manipulate an object's information.
Polymorphism the ability of different classes to respond to the same message and each implement it appropriately.
Using Objects
Internal, and User-defined Data-types (Classes)
Declaration, Definition, Initialization
Usage via Name, Reference, or Pointer
Manipulating Objects in Expressions via Operators or Functions
Objects as Function Parameters
Scope and Lifetime
Automatic, Dynamic and Static Objects
Arrays
double x[100]; // x[0] ... x[99]double x[3] = {1.1, 2.2, 3.3};double x[] = {1.1, 2.2, 3.3};
double m[3][3], m1[3][3], m2[3][3];// Code that initializes m1 and m2 ...
// m = m1 * m2for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { double sum = 0.0; for (int k = 0; k < 3; k++) { sum += m1[i][k] * m2[k][j]; } m[i][j] = sum; }}
C++ arrays use 0-origin subscripting and the elements are stored row-wise.
Warning: m[1,2] is a valid C++ expression, but it does not access a multidimensional array.
Multidimensional arrays are arrays of arrays.
int m[2][3] = {
{1,2,3},
{4,5,6}
};
m consists of two elements (rows), each a one-dimensional array of three elements.
int
1
m[0][0]
int
2
m[0][1]
int
3
m[0][2]
int
6
m[1][2]
int
5
m[1][1]
int
4
m[1][0]
Pointers
int* p;
int i = 3;
p = &i;
int j = 4;
P = &j;
*p = 5;
p = 0;
...
if (p != 0) cout << "Pointer " << p << " points to " << *p << endl;
int iint 3:int* p
int* :
int jint 4:int* p
int* : int i
int 3:
&x address of x*p indirection, value of object pointed to by p
Pointers and Arrays
float x[5];
x[0]: x[1]: x[2]: x[3]: x[4]:
float float float float floatfloat[5] x :
float*
Conversion
Whenever x is used in an expression -except as the operand of &, sizeof or to initialize a reference - it is convertedinto a pointer to the first element of thearray.x is of type array of float, but used as pointer to float.
Arithmetic on Pointers
x + 2
x points to x[0]
x+i points to x[i]
*(x+i)is equivalent to x[i]
x+i is equivalent to &x[i]
Integers can be added (or subtracted) to a pointer to an array element.
It acts as offset. The result is a pointer of the same type, which points to the element the specified number of elements away.
x[0]: x[1]: x[2]: x[3]: x[4]:
float float float float floatfloat[5] x :
float*
Conversion
float x[10];
// ... initialize x ...
float* left = &x[0];
float* right = &x[9];
while (left < right) {
float temp = *left;
*left++ = *right;
*right-- = temp;
}
float x[10];
float* p = &x[10];
while (p != x) *--p = 0.0;
const Pointers, Pointers to const Objects
const float pi = 3.14159;
const float* p = π
pi = 2; // Compile Error
*p = 2; // Compile Error
float a, b;
float* const p1 = &a;
*p1 = 10;
p1 = &b; // Compile Error
const float e = 2.718281828;
const float* const p2 = &e;
Read complex declarationsfrom the identifier backtoward the beginning of the statement.
C Programmers:Do not use #define to define symbolic constants,use const for individualconstants or enum forenumerations.
Runtime Array Size
// Create arrays with the
// desired number of elements
int n;
cin >> n;
double* const x = new double[n];
double* const y = new double[n];
// Read the data points
for (int i = 0; i < n; i++) {
cin >> x[i] >> y[i];
}
// Accumulate sums Sx and Sy
// in double precision
double sx = 0.0;
double sy = 0.0;
for (i = 0; i < n; i++) {
sx += x[i];
sy += y[i];
}
// Compute coefficients
double sx_over_n = sx / n; double stt = 0.0;
double b = 0.0;
for (i = 0; i < n; i++) {
double ti = x[i] - sx_over_n;
stt += ti * ti;
b += ti * y[i];
}
b /= stt;
double a = (sy - sx * b) / n; delete [] x;
delete [] y;
cout << a << " " << b << endl;
Pointer Operators*p Indirection
&x Pointer to object
a[i] Array subscript
p->m Class member selection
++p, p++Pre(Post)increment to next element
--p, p--Pre(Post)decrement to previous element
p+=n Increment by n elements
p-=n Decrement by n elements
p+n Offset by n elements
p-n Negative offset by n elements
new T Allocate object
new T[n]Allocate array of n objects
delete pDelete object
delete []p Delete array of objects
Classes
OO Programming Two Simple Classes An Array Class Class Templates Function Templates Exceptions Nested Classes Overview of C++ Programs
Today
Classes
Memory Management
Classes and Functions in Detail
Inheritance
Object-Oriented Programming
Object-oriented programming views a program as collection of agents, termed objects. Each object is responsible for specific tasks.
An object is an encapsulation of state (data members) and behavior (operations).
The behavior of objects is dictated by the object class. An object will exhibit its behavior by invoking a
method (similar to executing a procedure). Objects and classes extend the concept of abstract data
types by adding the notion of inheritance.
Variety of Classes
Domain Classes / Data Managers / Materials Classes whose principle responsibility is to maintain data
values
Data Sinks or Sources Objects that interface to data generators or consumers, but do
not hold data themselves
View or Observer Classes, Tool Classes Classes that provide the graphical display of an object, but are
not the object themselves
Helper Classes, Container Classes Classes that maintain little or no state information, but assist in
the execution of complex tasks
What Do Classes Represent
Classes that directly reflect concepts in the application domain.
Classes that are artifacts of the implementation.
User-level concepts (e.g. cars and trucks) Generalization of user-level concepts (e.g. vehicles) Hardware ressources (e.g. memory manager) System ressources (e.g. output streams) Helper classes (e.g. lists, queues, locks, ...)
Two Simple Classes
Classes are programmer-defined types.
class class-name; // Declaration
class class-name { // Definition
class-body
};
class Point {
...
};
Point a; // creates an object of type Point
Point square_vertices[4]; // creates an array of four Points
Remember the trailing semicolonin class definitions.
Mapping of Abstractions to Software
real world abstractions software
entity
attributes
behaviour
class Entityattribute1...
function1()...
CRC Cards for Class Design
CRC Class-Responsibility-Collaborator
Class-Name
Responsibility 1Responsibility 2....
Collaborator 1Collaborator 2...
Point Line
Create from coordinates Compute distance to another
point Compute distance to a line Get x- and y-coordinates
Line Point
Create from two points Create from point and direction Intersect with another line Compute distance to a point
Point definition
typedef double Number;
class Line;
class Point {public: Point(); // Create uninitialized Point(Number x, Number y); // Create from (x,y) Number distance(Point point); // Distance to another point Number distance(Line line); // Distance to a line Number x(); // Get x-coordinate Number y(); // Get y-coordinateprivate: Number the_x; // x-coordinate Number the_y; // y-coordinate};
Class members: data members
member functions
Using Point Objects
#include <iostream>using namespace std;#include "Point.h"
int main() { // Read the coordinates of a point and print the distance // from the origin to the point. Point origin(0, 0);
Number x; Point p1(1, 2); Number y; Point* p_ptr; cin >> x >> y; p_ptr = &origin; Point p(x, y); cout << p_ptr->distance(p1) << e cout << origin.distance(p) << endl; return 0;}
Point origin
Point
:
float the_y float0.0
:
float the_x float0.0
:
Data members of a class should be declared privateto ensure that they are only accessed from withinmember functions.
class Point {public: ... Point(Number x, Number y); ... private: Number the_x;
Number the_y; };
Point origin(0, 0);
Line definition
class Line {public: Line(Point p1, Point p2); // Line through p with tangent <xDir, yDir> Line(Point p, Number xDir, Number yDir); Point intersect(Line line); Number distance(Point point); // Smallest nonparallel angle (radians) static Number parallelism_tolerance; private: // ax + by + c = 0 Number a; Number b; Number c;};
Using Line Objects
// Vertical line through origin
Line l1(Point(0,0), Point(0,1));
//"anonymous" point objects are used for this line
// Line through (1,2) in direction <1,1>
Line l2(Point(1,2), 1, 1);
Point intersection;
intersection = l1.intersect(l2);
cout << "(" << intersection.x() << "," << intersection.y()
<< ")" << endl;
Member Function Definition
Point::Point(Number x, Number y) { the_x = x; the_y = y;}Point::Point() {}Number Point::x() { return the_x; }Number Point::y() { return the_y; }Number Point::distance(Point point) { Number x_dif = the_x - point.the_x;
//member – member of the object passed Number y_dif = the_y - point.the_y; return sqrt(x_dif * x_dif + y_dif * y_dif);}Number Point::distance(Line line) { double result = line.distance(*this); return result;}
Shared Objects inside Classes: staticPoint Line::intersect(Line line) {
// Intersect with line. If the angle between the two lines
// is less than the parallelism_tolerance, return the point
// at infinity. The parallelism test computes the square of
// the sin of the angle. We assume that the tolerance is
// small enough for sin(theta) to be approximately equal to
//theta.
Number det = a * line.b - line.a * b;
Number sinsq = (det * det) / ( (a*a + b*b) * (line.a*line.a
+ line.b*line.b) );
if (sinsq < parallelism_tolerance * parallelism_tolerance) {
return Point(FLT_MAX, FLT_MAX);
} else {
return Point( (b * line.c - line.b * c) / det,
(c * line.a - line.c * a) / det );
}
}
Number Line::parallelism_tolerance = .01745; // 1 degree (in rad)
Line::parallelism_tolerance = .02618; // 1.5 degrees (in radians)
An Array Class: Operator Member Functions
#include <iostream>#include "SimpleFloatArray.h"
void linefit() { // Create arrays with the desired number of elements int n; std::cin >> n; SimpleFloatArray x(n); SimpleFloatArray y(n);
// Read the data points for (int i = 0; i < n; i++) { std::cin >> x[i] >> y[i]; } // ... same as before ... }
Runtime sizing with size given as constructor argument.
Access to element i in array x by evaluation of x[i].
Automatic management of dynamically allocated memory associated with the array.
Automatically copy array elements when arrays are copied.
Assign one array to another, and assign one float value to every array element.
Ask array's size and resize an array dynamically.
Runtime sizing with size given as constructor
argument.
class SimpleFloatArray {
public:
SimpleFloatArray(int n); // Create array of n elements
SimpleFloatArray(); // Create array of 0 elements
...
private:
int num_elts; // Number of elements
float* ptr_to_data; // Pointer to built-in array of elements
};
SimpleFloatArray::SimpleFloatArray(int n) {
num_elts = n;
ptr_to_data = new float[n];
}
SimpleFloatArray::SimpleFloatArray() {
num_elts = 0;
ptr_to_data = 0;
}
Access to element i in array x by evaluation of
x[i].
class SimpleFloatArray {
public:
...
float& operator[](int i); // Subscripting
...
private:
int num_elts; // Number of elements
float* ptr_to_data; // Pointer to built-in array of elements
};
float& SimpleFloatArray::operator[](int i) {
return ptr_to_data[i];
}
Access to element i in array x by evaluation of
At(i).
class SimpleFloatArray {
public:
...
float& At(int i); // Subscripting
...
private:
int num_elts; // Number of elements
float* ptr_to_data; // Pointer to built-in array of elements
};
float& SimpleFloatArray::At(int i) {
return ptr_to_data[i];
}
SimpleFloatArray d(100);
cout << d[50] << .... cout << d.At(50),...
Automatic management of dynamically allocated
memory associated with the array: destructor
class SimpleFloatArray {
public:
...
~SimpleFloatArray(); // Destructor: destroy array
...
private:
int num_elts; // Number of elements
float* ptr_to_data; // Pointer to built-in array of elements
};
SimpleFloatArray::~SimpleFloatArray() {
delete [] ptr_to_data;
}
When invoked with a null pointer, delete and delete [] have no effect.
Automatically copy array elements when arrays are
copied: copy constructorclass SimpleFloatArray {
public:
...
SimpleFloatArray(const SimpleFloatArray&); // Copy Constructor
...
private:
int num_elts; // Number of elements
float* ptr_to_data; // Pointer to built-in array of elements
void copy(const SimpleFloatArray& a); // Copy in elements from a
};
SimpleFloatArray::SimpleFloatArray(const SimpleFloatArray& a) {
num_elts = a.num_elts;
ptr_to_data = new float[num_elts];
copy(a); // Copy a's elements
}
void SimpleFloatArray::copy(const SimpleFloatArray& a) {
// Copy a's elements into the elements of *this
float* p = ptr_to_data + num_elts;
float* q = a.ptr_to_data + num_elts;
while (p > ptr_to_data) *--p = *--q;
};
or
void SimpleFloatArray::copy(const SimpleFloatArray& a) {
// Copy a's elements into the elements of *this
for(int i=0; i<num_elts; ++i)
ptr_to_data[i] = a.ptr_to_data[i];
}
Assign one array to another, and assign one float
value to every array element: copy assignment operator
class SimpleFloatArray {
public:
...
SimpleFloatArray& operator=(const SimpleFloatArray&);
...
private:
int num_elts; // Number of elements
float* ptr_to_data; // Pointer to built-in array of elements
void copy(const SimpleFloatArray& a); // Copy in elements from a
};
SimpleFloatArray&
SimpleFloatArray::operator=(const SimpleFloatArray& rhs) {
if ( ptr_to_data != rhs.ptr_to_data ) {
setSize( rhs.num_elts );
copy(rhs);
}
return *this;
} Assignment should work correctly when left and rightoperands are the same object.
Ask array's size and resize an array dynamically.
class SimpleFloatArray {
public:
...
SimpleFloatArray& operator=(const SimpleFloatArray&);
SimpleFloatArray& operator=(float); // Scalar assignment
int numElts(); // Number of elements
void setSize(int n); // Change size
private:
int num_elts; // Number of elements
float* ptr_to_data; // Pointer to built-in array of elements
void copy(const SimpleFloatArray& a); // Copy in elements from a
};
Class Templates
class SimpleFloatArray {
public:
SimpleFloatArray(int n);
SimpleFloatArray();
SimpleFloatArray(const SimpleFloatArray&);
~SimpleFloatArray();
float& operator[](int i);
int numElts();
SimpleFloatArray& operator=(const SimpleFloatArray&);
SimpleFloatArray& operator=(float);
void setSize(int n);
private:
int num_elts;
float* ptr_to_data;
void copy(const SimpleFloatArray& a);
};
Class Template Definition
template<class T>
class SimpleArray {
public:
SimpleArray(int n);
SimpleArray();
SimpleArray(const SimpleArray<T>&);
~SimpleArray();
T& operator[](int i);
int numElts();
SimpleArray<T>& operator=(const SimpleArray<T>&);
SimpleArray<T>& operator=(T);
void setSize(int n);
private:
int num_elts;
T* ptr_to_data;
void copy(const SimpleArray<T>& a);
};
Class Templates: Usage
#include <iostream>#include "SimpleArray.h"
void linefit() { // Create arrays with the desired number of elements int n; std::cin >> n; SimpleArray<float> x(n); SimpleArray<float> y(n);
// Read the data points for (int i = 0; i < n; i++) { std::cin >> x[i] >> y[i]; } // ... same as before ... }
Member function definition for class templates
void SimpleFloatArray::copy(const SimpleFloatArray& a) {
// Copy a's elements into the elements of *this
float* p = ptr_to_data + num_elts;
float* q = a.ptr_to_data + num_elts;
while (p > ptr_to_data) *--p = *--q;
}
template<class T>
void SimpleArray<T>::copy(const SimpleArray<T>& a) {
// Copy a's elements into the elements of *this
T* p = ptr_to_data + num_elts;
T* q = a.ptr_to_data + num_elts;
while (p > ptr_to_data) *--p = *--q;
}
Overview of C++ Programs
File sys.h File A.h File B.h
File main.C
#include<sys.h>
#include"A.h"
#include"B.h"
main() {
...
}
File A.C
#include"A.h"
(Code for A)
File B.C
#include"B.h"
(Code for B)
Class A Class B
Memory Management
Object Life Cycle: Construction
Allocation Preinitialization Initialization
Use Destruction
Cleanup Post cleanup Deallocation
Object Lifetime Static Objects Automatic Objects Dynamic Objects Preventing Dangling
References and Garbage
Object Lifetime
Static objectsallocated once and not freed until program termination.
Automatic objectsallocated when their declarations are executed and freed
automatically when the block containing them terminates.
Dynamic objectsallocated and freed in arbitary order under the programmers
control.
Class Noisy to trace object con/de struction
#include "Noisy.h"
Noisy func(Noisy n) { std::cout<< "inside func\n"; return n;}
int main() {
Noisy x("x");
std::cout<<"\ncalling func\n";
Noisy c = func(x);
std::cout<<"after func\n";
std::cout << '\n';
return 0;
}
constructing: (name=x id=0)
calling func
copy construct: (name=x id=1) from: (name=x id=0)
inside func
copy construct: (name=x id=2) from: (name=x id=1)
destroying: (name=x id=1)
after func
destroying: (name=x id=2)
destroying: (name=x id=0)
Noisy.h#ifndef NOISY_H
#define NOISY_H
#include <iostream>
#include <string>
#include <sstream>
class Noisy {
static long objects_created;
long id;
std::string name;
public:
Noisy(std::string n=""): id(objects_created++), name(n) {
if (name == "") {
std::ostringstream n;
n << "obj_" << id ;
name = n.str();
}
std::cout<<"constructing: " << *this << '\n';
}
Noisy(const Noisy& n): id(objects_created++), name(n.name) {
std::cout<<"copy constuct: " << *this << " from: "
<< n << '\n';
}
~Noisy() {std::cout<<"destroying: " << *this << std::endl;}
Noisy& operator=(const Noisy& rhs) {
std::cout<<"assignment: " << *this << " = " << rhs << '\n';
name = rhs.name;
return *this;
}
friend std::ostream& operator<<(std::ostream& os, const Noisy &n) {
return os << "(name=" << n.name << " id=" << n.id << ')';
}
}; // class Noisy
long Noisy::objects_created = 0;
#endif
Static Objects
are allocated once and are not freed until the program terminates.
Scope: class scopeclass A { ...; static int a; ...};
A::a = 1;
file scope with external linkageextern int a;
int a = 1;
file scope with internal linkagestatic int a;
local scope{...; static int a = 1; ...}
Use the extern keyword to declare file scope names with external linkage; omit the extern keyword on their definitions.extern int a; // Declaration, external linkageextern Complex a; // Decalration, external linkage
int a = 1; // Definition, initialized to 1Complex c1; // Definition, default constructor
Avoid file scope objects with external linkage; use classscop instead.
extern const double c;extern const double k;
const double c = 3.00e8;const double k = 1.38e-23;
namespace PhysicalConstants {public: static const double c; ...}const double PhysicalConstants::c = 3.00e8;...
Prefer namespace scope names to file scope names.
static Noisy s1("s1"); // File scope static object.
void fnc(int j) { cout << "-- starting fnc(" << j << ") --" << endl; static Noisy s2("s2"); if (j == 2) { static Noisy s4("s4"); } for (int i = 1; i <= 2; i++) { cout << "-- loop i=" << i << " --" << endl; static Noisy s3("s3"); } cout << "-- returning from fnc(" << j << ") --" << endl;}
int main() { cout << "-- main starts --" << endl; fnc(1); fnc(2); cout << "-- main ends --" << endl; return 0;} static Noisy s5("s5"); // File scope static object.
Automatic Objects
int main() { cout << "-- main starts --" << endl; Noisy a1("a1"); for (int i = 1; i <= 2; i++) { cout << " -- loop i= " << i << " -- " << endl; Noisy a2("a2"); // Object created each loop iteration. if (i == 2) { Noisy a3("a3"); // Object created if i == 2. } } cout << "-- loop is done -- " << endl; Noisy a4("a4"); cout << "-- main ends --" << endl; return 0;};
void f2() {
cout << "-- f2 starts -- " << endl;
Noisy c("c");
throw "exception";
cout << "-- f2 ends -- " << endl;
}
void f1() {
cout << "-- f1 starts -- " << endl;
Noisy a("a");
f2();
Noisy b("b");
cout << "-- f1 ends -- " << endl;
}
int main() {
cout << "-- main starts --" << endl;
try {
f1();
} catch(const char*) {
cout << "Exception caught." << endl;
}
cout << "-- main ends --" << endl;
return 0;
}
Dynamic Objects
Constructionint* p = new int;
int* q = new int(3);
Rational* r = new Rational(8,9);
int* a = new int[n];
Destructiondelete p;
delete q;
delete r;
delete [] a;
The worst problems arise from bad deletions: deleting an object more than once deleting a pointer not obtained by new using the wrong form of new
Provide a default constructor for every class possible.
Match every invocation of new with exactly one invocation of delete of the same kind.
Preventing Dangling References and Garbage
Dangling Class Membersclass Dangle {
public:
Dangle() { p = new int(0);}
~Dangle() { delete p; }
private:
int* p;
};
void f() {
Dangle a;
Dangle b = a;
}
Dangle a
Dangle b
:
:
int0
int* p :int*
int* p :int*
Provide a copy constructor and an assignment operator for classes with a pointer data member that is deleted by the destructor.
Dangling Pointers to Automatic Objects
void dangle(int j) {
int* p;
if (j < 100) {
// Dangling reference created when this block terminates
int iarray[100];
p = iarray;
}
else p = new int[j];
for (int i = 0; i < j; i++) p[i] = i;
}
Avoid pointers to automatic objects.
Pointers Left Dangling by Function Calls
void f(int* x) {
// ...
delete [] x; // Causes dangling reference
}
void dynamic_dangle(int size) {
int* iarray = new int [size];
f(iarray);
for (int i = 0; i < size; i++) cout << iarray[i];
}
Don't delete a functions argument.
Garbage Memory / Memory Leaks (failing to delete) is the opposite of the dangling
reference problem.
Hold pointers in class objects and pair new and deletein the class's constructors and destructors.
The Uses Relationship
If an object of one class sends a message to an object of another class, the first object is said to have a uses relationship with the second class.
How can the first object know the name of the object it wants to use?
Carget_gasoline(50€)
Where ?
get_gasoline(50€, Aral)Car
class Car {public: void get_gasoline(GasStation &, double);};
void Car::get_gasoline(GasStation &s, double money) { ... gas_received = s.give_gasoline(money); ...}
GasStation station1(...);Car car1(...);car1.drive(...);car1.get_gasoline(station1, 50);car1.drive(...);
by parameter
Car1get_gasoline(50€) The gas
station
give_gasoline(50€)
give_gasoline(40€)
Car2
GasStation theGasStation(...);class Car {public: void get_gasoline(double);};
void Car::get_gasoline(double money) { ... gas_received = theGasStation.give_gasoline(money); ...}
Car car1(...);car1.drive(...);car1.get_gasoline(50);
by global object
class Car {public: void get_gasoline(double);};
void Car::get_gasoline(double money) { ... GasStation station(...); gas_received = station.give_gasoline(money); ...}
Car car1(...);car1.drive(...);car1.get_gasoline(50);
create a local station
class Car {private: GasStation *station;public: Car(GasStation *s,...): station(s) {...} void get_gasoline(double);};
void Car::get_gasoline(double money) { ... gas_received = station->give_gasoline(money); ...}
GasStation s(...);Car car1(s,...);car1.drive(...);car1.get_gasoline(50);
by construction
Carget_gasoline(50€)
Mapget_station()
by third-party class
class Map {private: GasStation *quadrant[4];public: GasStation* get_station(int,int);};GasStation* Map::get_station(int x, int y) { if(x>0) return (y>0) ? quadrant[0]:quadrant[3]; else return (y>0) ? quadrant[1]:quadrant[2];}class Car {private: Map myMap;public: void get_gasoline(double);};void Car::get_gasoline(double money) { GasStation *station = myMap.get_station(x,y); gas_received = station->give_gasoline(money); ...}
by containmentclass Car {private: GasStation myStation;public: void get_gasoline(double);};void Car::get_gasoline(double money) { gas_received = station.give_gasoline(money); ...}
by inheritance
class Car: public GasStation {public: void get_gasoline(double);};void Car::get_gasoline(double money) { gas_received = give_gasoline(money); ...}
Functions and Classes Member Functions and Overloading Initialization Copying Conversion Operator Functions Assignment Special Operators Destruction Static Member Functions Friend Functions I/O Operators for Classes
Member Functions and Overloading
class Point {
public:
Point(); // Create uninitialized
Point(Number x, Number y); // Create from (x,y)
Number distance(Point point) const; // Distance to another point
Number distance(Line line) const; // Distance to a line
Number& x(); // Reference to x-coordinate
Number x() const; // Get x-coordinate
Number& y(); // Reference to y-coordinate
Number y() const; // Get y-coordinate
Number angle(Point p1, Point p3) const;
private:
Number the_x; // x-coordinate
Number the_y; // y-coordinate
};
Point p1(2, 3);
cout << p1.x() << endl; // Uses non-const x()
p1.x() = 1; // Uses non-const x()
p1.y() = 2; // Uses non-const y()
const Point p2 = p1;
cout << p2.x() << endl; // Uses const x()
p2.x() = 3; // Compile Error
p2.y() = 4; // Compile Error
Declare member functions that are not meant to altermember data const.
Initialization: Constructors
class Circle {public: Circle(double radius); // Of specified radius centered at origin Circle(Point center, double radius); // From center and radius Circle(Point p1, Point p2, Point p3);// From three points on circle Circle(LineSegment chord, Point p); // From chord and point on cir. Circle(LineSegment diameter); // From diameter // ...private: Point the_center; double the_radius;};
Circle::Circle(double radius) : the_center(0, 0) { the_radius = radius;}
A member initializer does not need to be provided for a data member in
two cases:
1. The data member is of a built-in type. Its value is undefined.
2. The data member is of a programmer defined type. The default constructor is called.
A member initializer must be provided for a data member, if:
1. There is no default constructor for the member's type.
2. The data member is a reference.
3. The data member is declared const.
Avoid using the value of one member in the initializationfor another member.
Member initializations should appear in the order in which the members are declared.
Copying: Copy Constructor
Objects can be copied by assignment or by initialization: operator=(const C&) Copy constructor: C::C(const C&), C::(C&)
shallow copy versus deep copy C++ default copy constructor: shallow copy
Circle c1(Point(1,2), 10); // No copy
Circle c2(c1); // c2 initialized by copying c1
Circle c3 = 10; // Create Circle(10.0) then initialize
// c3 from it
shallow copy
aSimpleFloatArray
int 5;
float*
bSimpleFloatArray
int 5;
float*
float1.0
float9.0
float5.0
float2.0
float1.0
deep copy
aSimpleFloatArray
int 5;
float*
bSimpleFloatArray
int 5;
float*
float1.0
float9.0
float5.0
float2.0
float1.0
float1.0
float9.0
float5.0
float2.0
float1.0
Automatically copy array elements when arrays are
copied: copy constructorclass SimpleFloatArray {
public:
...
SimpleFloatArray(const SimpleFloatArray&); // Copy Constructor
...
private:
int num_elts; // Number of elements
float* ptr_to_data; // Pointer to built-in array of elements
void copy(const SimpleFloatArray& a); // Copy in elements from a
}
SimpleFloatArray::SimpleFloatArray(const SimpleFloatArray& a) {
num_elts = a.num_elts;
ptr_to_data = new float[num_elts];
copy(a); // Copy a's elements
}
A class that has built-in pointer member data not referring to data shared with other objects should havea copy constructor.
The argument to a copy constructor should be a constreference.
Conversions
A constructor that can take a single argument defines a user-defined conversion.
A conversion function is a member function with the target type name following the keyword operator.
Such conversions are used in the argument matching process and in any other context in which C++ does automatic type conversion.
class Boolean {
public:
enum constants { false = 0, true = 1 };
// Construction.
Boolean() {} // Construct uninitialized.
Boolean(int i) : v(i != 0) {} // Initialize to (i != 0).
Boolean(float f) : v(f != 0) {} // Initialize to (f != 0).
Boolean(double d) : v(d != 0) {} // Initialize to (d != 0).
Boolean(void* p) : v(p != 0) {} // Initialize to (p != 0).
operator int() const{ return v; } // Conversion to int.
Boolean operator!() const { return !v; } // Negation.
private: Boolean b1(Boolean::true); // Boolean(int)
char v; Boolean b2(3); // Boolean(int)
}; int* p_i =new int(3);
Boolean b3(p_i); // Boolean(void *)
Boolean b4(3.0); // Boolean(float)
Boolean has_real_solution(double a, double b, double c) {
// Does ax**2 + bx + c = 0 have a real solution?
return sqr(b) >= 4 * a * c;
}
if ( has_real_solution(a, b, c) ) {
// ...
return 1;
}
Provide conversion from one class to another with either the copy constructor or a conversion function, but not both.
class ComplexFloat {public: // ... operator ComplexDouble() const; // ...};class ComplexDouble {public: // ... ComplexFloat cf; ComplexDouble(const ComplexFloat&); ... // ... ComplexDouble cd = cf;};
class ComplexInt {
public:
// ...
};
class ComplexFloat {
public:
// ...
ComplexFloat(ComplexInt);
// ...
};
class ComplexDouble {
public: ComplexDouble(ComplexInt); ComplexInt ci;
ComplexDouble(ComplexFloat); // ...
// ... double a = arg(ci);
};
// Principal value of the argument of z
extern float arg(ComplexFloat z);
// Principal value of the argument of z
extern double arg(ComplexDouble z);
Minimize the number ofuser-defined conversionsfrom any given type.
Working code can be broken by introducing new classes.
class Array {
public:
Array(int n); // Create n element array of int's
Array(const Array&); // Copy array
Array& operator=(const Array&); // Assign array
// ...
};
// ...
Array a(5);
a = 5;
ComplexDouble toComplexDouble() const;
Avoid single argument constructors when possible.
Provide few user-defined conversions.
Declare single argument constructors when explicit.
Operator Functions Operators in C++ are specified (declared) and
implemented (defined) in terms of equivalent functions, but they are used as operators.
Complex& Complex::operator*=(const Complex& rhs) {
float original_real_part = real_part;
real_part = real_part * rhs.real_part - imag_part * rhs.imag_part;
imag_part = imag_part * rhs.real_part + original_real_part
* rhs.imag_part;
return *this;
}
Complex a(1,1);
Complex b(2,3);
a *= b; a.operator*=(b);
All C++ operators can be defined for class objects, except:
. .* :: ?: new delete sizeof No new operator symbols can be invented. The 'arity', precedence and associativity of
programmer-defined operators follows that of built-in operators.
An unary operator can be defined by either a member function with no arguments or as a global function with one argument, etc.
Give operators conventional definitions.
class Complex{
public:
...
Complex operator+(const Complex&) const;
...
}
Complex a;
Complex b;
b = a + 3.0; // 3.0 is converted to Complex(3.0)
b = 3.0 + b; // Compile error (3.0).operator+(b)
Complex operator+(const Complex&, const Complex&);
Complex a;
Complex b;
b = a + 3.0; // 3.0 is converted to Complex(3.0)
b = 3.0 + b; // 3.0 is converted to Complex(3.0)
Declare symmetric binary operators as global functions.
Declare asymmetric binary operators and unary operators that modify their operand as member functions.
Define symmetric binary operators to call theircorresponding asymmetric assignment operator.
Complex operator*(const Complex& lhs, const Complex& rhs) { Complex result(lhs); return result *= rhs;}
Never overload &&, || or , . Function call semanticdiffers from short-circuit semantic because all parametersmust be evaluated and the order of evaluation is unspecified.
Assignment
Assignment member functions should work correctlywhen the left and the right operands are the same.
A class that has built-in pointer member data that are not meant to point to shared data should have an assignment operator.
The argument to an assignment operator should be a const reference.
Assignment operator functions should return a reference to their left operand.
Static Member Functions
class ComplexFloat {
public:
ComplexFloat(float r, float i);
static ComplexFloat fromFile(istream& input = cin);
// ...
};
ComplexFloat ComplexFloat::fromFile(istream& input) {
float r;
input >> r;
float i;
input >> i;
return ComplexFloat(r, i);
}
ComplexFloat c = ComplexFloat::fromFile();
Friend Functions
class A;
class B {
double g() const;
double g();
double g(int);
void h();
};
class C {
public:
friend int f(double); // Global function: int f(double)
friend double B::g() const; // Member of B: double g() const
// Note: only one of B's g members is a friend
friend class D; // All members of D
friend A; // All members of A; A already declared
private:
friend void B::h(); // Member of B: void h()
};
Friend Usage Example: Container Iterator
int main() {
// Read list of values and find minimum.
List<float> list;
float val;
float minval = FLT_MAX; // Max float value, from float.h
while ( cin >> val) {
if (val < minval) minval = val;
list.add(val);
}
// Normalize values and write out.
for (ListIterator<float> i(list); i.more(); i.advance()) {
cout << i.current() - minval << endl;
}
...
template<class T>class List {public: List() : first(0), last(0) {} ... void add(T x) { if (first == 0) first = last = new Node(x); else last = last->link = new Node(x); } void remove(T x); // ... friend ListIterator<T>;private: class Node { public: Node(T x) : link(0), datum(x) {} Node* link; T datum; }; Node* first; Node* last; };
template<class T>class ListIterator {
public:
ListIterator(const List<T>& list) : cur(list.first) {}
bool more() const { return cur != 0; }
T current() const { return cur->datum; }
void advance() { cur = cur->link; }
private:
List<T>::Node* cur;
};
Exceptions: throwclass SubscriptRangeError {
public:
SubscriptRangeError(int i);
int badSubscript();
private:
int subscript;
};
inline SubscriptRangeError::SubscriptRangeError(int i) {
subscript = i;
}
inline int SubscriptRangeError::badSubscript() {
return subscript;
}
...
template<class T>
T& CheckedSimpleArray<T>::operator[](int i) {
if (i<0 || i>=num_elts) throw SubscriptRangeError(i);
...
Exceptions: try, catch
try {
int n;
cin >> n;
CheckedSimpleArray<float> a(n);
...
int j;
cin >> j;
float x = 2.1 * a[j];
...
}
catch(SubscriptRangeError e) {
cerr << "Bad subscript = " << e.badSize() << endl;
}
Use exceptions to decouple the treatment of "errors" fromthe code dealing with the ordinary processing.
Nested Classes
class Outer {
public:
...
class Inner1 {
...
}
...
private:
...
class Inner2 {
...
}
...
}
Within the enclosing class a nested class can be referenced to using just ist name.
From outside the enclosing class, a nested class may be refrenced via the scope resolution operator ::
Outer::Inner1
Use class nesting to group related classes that work together.
Object-Oriented Programming
Object-oriented programming views a program as collection of agents, termed objects. Each object is responsible for specific tasks.
An object is an encapsulation of state (data members) and behavior (operations).
The behavior of objects is dictated by the object class. An object will exhibit its behavior by invoking a
method (similar to executing a procedure). Objects and classes extend the concept of abstract data
types by adding the notion of inheritance.
Inheritance in C++
base / derived class
public / private inheritance
non-virtual / virtual / pure virtual member functions
concrete / abstract classes
Mammal
speak()
Dog
bark()
Cat
mew()
class Mammal {public: speak();}
class Dog: public Mammal {public: void bark();}
class Cat: public Mammal {public: void mew();}
Public Inheritance, Non-Virtual Members
class Mammal { // base class
public:
void speak() {cout << "can't speak" << endl;}
};
class Dog : public Mammal { // derived class
public:
void speak() {cout << "wouf" << endl;}
void bark() {cout << "wouf wouf" << endl;}
};
class Cat : public Mammal { // derived class
public:
void mew() {cout << "mew mew" << endl;}
};
Mammal fred;
fred.speak();
Dog lassie;
lassie.speak();
lassie.bark();
Cat sue;
sue.speak();//since speak is not defined for Cat, //the output is "cant speak"
sue.mew();
Mammal* x = new Dog();// this is allowed !
x->speak();// "cant speak"
x = &sue;
x->speak();
Public Inheritance, Virtual Members
class Mammal { // base class
public:
virtual void speak() {cout << "can't speak" << endl;}
};
class Dog : public Mammal { // derived class
public:
void speak() {cout << "wouf" << endl;}
void bark() {cout << "wouf wouf" << endl;}
};
class Cat : public Mammal { // derived class
public:
void mew() {cout << "mew mew" << endl;}
};
Mammal fred;
fred.speak();
Dog lassie;
lassie.speak();
Cat sue;
sue.speak();
Mammal* x = new Dog();
x->speak();// "wouf" because speak is virtual
// x only can use "Mammal" functions
// because its a mammal
x = &sue;
x->speak();
Virtual member functions to supportpolymorphism.
Public Inheritance, Pure Virtual Members
class Mammal { // abstract base class
public:
virtual void speak() = 0; //pure virtual member function
};
class Dog : public Mammal { // derived class
public:
void speak() {cout << "wouf" << endl;}
void bark() {cout << "wouf wouf" << endl;}
};
class Cat : public Mammal { // derived class
public:
void speak() {cout << "mew" << endl;}
void mew() {cout << "mew mew" << endl;}
};
// Mammal fred;
// fred.speak();
Dog lassie;
lassie.speak();
Cat sue;
sue.speak();
Mammal* x[n];
x[0] = &lassie;
x[1] = &sue;
for(int i=0;i<n;i++) {
x[i]->speak();
}
x[0]->bark(); // Error
Abstract classes serve as interfaces.
"abstract"
Mammal
speak()
Dog Cat
Private Inheritance
class Mammal {
public:
void speak() {cout << "can't speak" << endl;}
};
class Dog : private Mammal {
public:
void bark() {speak();}
};
class Cat : public Mammal {
public:
void mew() {cout << "mew mew" << endl;}
}
Mammal fred;
fred.speak();
Dog lassie;
lassie.bark();
lassie.speak(); // Error
Cat sue;
sue.speak(); //this works
Private inheritance to share implementation.
Public Inheritance, Virtual Members
class Mammal { // base class
public:
virtual void speak() {cout << "can't speak" << endl;}
};
class Dog : public Mammal { // derived class
public:
void speak() {cout << "wouf" << endl;}
void bark() {cout << "wouf wouf" << endl;}
};
class Cat : public Mammal { // derived class
public:
void mew() {cout << "mew mew" << endl;}
};
Mammal fred;
fred.speak();
Dog lassie;
lassie.speak();
Cat sue;
sue.speak();
Mammal* x = new Dog();
x->speak();
x = &sue;
x->speak();
Virtual member functions to supportpolymorphism.
Access to Base Class Members
class One {
public:
int element1;
protected:
int element2;
private:
int element3;
};
class Two : public One {
};
clients of Two
clients of Three
clients of Four
...
class Three : private One {
};
class Four : protected One {
};
class Five : public Three {
};
class Six: public Four {
};
dynamic_cast<T>(), typeinfo#include <typeinfo>
Dog lassie;
Cat sue;
Mammal* m[3];
m[0] = &lassie;
m[1] = &sue;
...
for ( i=0;i<3;i++) cout << typeid(m[i]).name() << ", ";
cout << endl;
for ( i=0;i<3;i++){
Dog* d = dynamic_cast<Dog *>(m[i]);//0 or a valid pointer
if(d) d->bark(); // if the pointer is valid, bark
}
Forms of Inheritance
Specialization: The derived class is a subtype, a special case of the base class.
Specification: The base class defines behavior that is implemented in the
derived class.
Construction: The derived class makes use of the behavior provided by the
base class, but is not a subtype of the base class.
Generalization: The derived class modifies some of the methods of the base.
Extension: The derived class adds new functionality to the base, but does not
change any inherited behavior.
Limitation: The derived class restricts the use of some of the behavior
inherited from the base.
Variance: The derived class and the base class are variants of each other,
and the base/derived class relationship is arbitrary.
Combination: The derived class inherits from more than one base class.