Wednesday 25 July 2012

Classes

Classes

Classes provide data abstraction, the ability to create new types and hide their implementation in order to improve maintainability. A class is a data structure and an associated set of member functions (methods) and related type declarations which can be associated with the class or instances (objects) of the class. A class is divided into a public interface, visible wherever the class or its instances are visible, and a private implementation visible only to member functions of the class.
class T { // Create a new type T
private: // Members are visible only to member functions of T (default)
public: // Members are visible wherever T is visible
// Type, object, and function declarations
};
T::m; // Member m of type T
T x; // Create object x of type T
x.m; // Member m of object x
T* p=&x; p->m; // Member m of object pointed to by p
Typically the data structure is private, and functionality is provided by member functions. Member function definitions should be separated from the declaration and written outside the class definition, or else they are assumed to be inline (which is appropriate for short functions). A member function should be declared const (before the opening brace) if it does not modify any data members. Only const member functions may be called on const objects. class Complex { // Represents imaginary numbers
private:
double re, im; // Data members, represents re + im * sqrt(-1)
public:
void set(double r, double i) {re=r; im=i;} // Inlined member function definition
double real() const {return re;} // const - does not modify data members
double imag() const; // Declaration for non-inlined function
};
double Complex::imag() const {return im;} // Definition for imag()
int main() {
Complex a, b=a; // Objects of type Complex
a.set(3, 4); // Call a member function
b=a; // Assign b.re=a.re; b.im=a.im
b==a; // Error, == is not defined
cout << a.re; // Error, re is private
cout << a.real(); // OK, 3
cout << Complex().real(); // OK, prints an undefined value
Complex().set(5, 6); // Error, non-const member called on const object

A class has two special member functions, a constructor, which is called when the object is created, and a destructor, called when destroyed. The constructor is named class::class, has no return type or value, may be overloaded and have default arguments, and is never const. It is followed by an optional initialization list listing each data member and its initial value. Initialization takes place before the constructor code is executed. Initialization might not be in the order listed. Members not listed are default-initialized by calling their constructors with default arguments. If no constructor is written, the compiler provides one which default-initializes all members. The syntax is:
class::class(parameter list): member(value), member(value) { code...}

The destructor is named class::~class, has no return type or value, no parameters, and is never const. It is usually not needed except to return shared resources by closing files or deleting memory. After the code executes, the data members are destroyed using their respective destructors in the reverse order in which they were constructed.
class Complex {
public:
Complex(double r=0, double i=0): re(r), im(i) {} // Constructor
~Complex() {} // Destructor
// Other members...
};
Complex a(1,2), b(3), c=4, d; // (1,2) (3,0) (4,0) (0,0)

A constructor defines a conversion function for creating temporary objects. A constructor that allows 1 argument allows implicit conversion wherever needed, such as in expressions, parameter passing, assignment, and initialization.
Complex(3, 4).real(); // 3
a = 5; // Implicit a = Complex(5) or a = Complex(5, 0)

void assign_if(bool, Complex&, const Complex&);
assign_if(true, a, 6); // Implicit Complex(6) passed to third parameter
assign_if(true, 6, a); // Error, non-const reference to Complex(6), which is const

Operators may be overloaded as members. The expression aXb for operator X can match either operator X(a, b) (global) or a.operator X(b) (member function), but not both. Unary operators omit b. Operators =, [], and -> can only be overloaded as member functions.
class Complex {
public:
Complex operator + (const Complex& b) const { // const because a+b doesn't change a
return Complex(re+b.re, im+b.im);
}
// ...
};

Complex operator - (const Complex& a, const Complex& b) {
return Complex(a.real()-b.real(), a.imag()-b.imag());
}

Complex a(1, 2), b(3, 4);
a+b; // OK, a.operator+(b) == Complex(4, 6)
a-b; // OK, operator-(a, b) == Complex(-2, -2)
a+10; // OK, Complex(1, 12), implicit a+Complex(10, 0)
10+a; // Error, 10 has no member operator+(Complex)
a-10; // OK, Complex(1, -8)
10-a; // OK, Complex(7, -4)

The member function (+) has the advantage of private access (including to other objects of the same class), but can only do implicit conversions on the right side. The global function (-) is symmetrical, but lacks private access. A friend declaration (in either the private or public section) allows private access to a global function.
class Complex {
friend Complex operator-(const Complex&, const Complex&);
friend class T; // All member functions of class T are friends
// ...
};

A conversion operator allows implicit conversion to another type. It has the form of a member function named operator T() const with implied return type T. It is generally a good idea to allow implicit conversions in only one direction, preferably with constructors, so this member function is usually used to convert to pre-existing types.
class Complex {
public:
operator double() const {return re;}
// ...
}

Complex a(1, 2);
a-10; // Error, double(a)-10 or a-Complex(10) ?
a-Complex(10); // Complex(-9, 2);
double(a)-10; // -9

An explicit constructor does not allow implicit conversions.
class Complex {
explicit Complex(double r=0, double i=0);
// ...
};

Complex a=1; // Error
Complex a(1); // OK
a-10; // OK, double(a)-10 = -9
a-Complex(10); // OK, Complex(-9, 0)

A class or member function may be templated. The type parameter must be passed in the declaration for objects of the class.
template <class T>
class Complex {
T re, im;
public:
T real() const {return re;}
T imag() const {return im;}
Complex(T r=0, T i=0);
friend Complex<T> operator - (const Complex<T>&, const Complex<T>&);
};

template <class T>
Complex<T>::Complex(T r, T i): re(r), im(i) {}

Complex<int> a(1, 2); // Complex of int
Complex<double> b(1.0, 2.0); // Complex of double
a=a-Complex<int>(3, 4); // Complex<int>(-2, -2)
Complex<Complex<double> > c(b, b); // Note space, not >>
c.real().imag(); // 2.0
Templates can have default arguments and int parameters. The argument to an int parameter must be a value known at compile time. template <class T, class U=T, int n=0> class V {};
V<double, string, 3> v1;
V<char> v2; // V<char, char, 0>

Classes define default behavior for copying and assignment, which is to copy/assign each data member. This behavior can be overridden by writing a copy constructor and operator= as members, both taking arguments of the same type, passed by const reference. They are usually required in classes that have destructors, such as the vector<T>-like class below. If we did not overload these, the default behavior would be to copy the data pointer, resulting in two Vectors pointing into the same array. The assignment operator normally returns itself (*this) by reference to allow expressions of the form a=b=c;, but is not required to do so. this means the address of the current object; thus any member m may also be called this->m within a member function.
template <class T>
class Vector {
private:
T* data; // Array of n elements
int n; // size()
public:
typedef T* iterator; // Vector::iterator means T*
typedef const T* const_iterator; // Iterators for const Vector
int size() const {return n;} // Number of elements
T& operator[](int i) {return data[i];} // i'th element
const T& operator[](int i) const {return data[i];} // i'th element of const Vector
iterator begin() {return data;} // First, last+1 elements
iterator end() {return data+size();}
const_iterator begin() const {return data;} // Const versions
const_iterator end() const {return data+size();}
Vector(int i=0): data(new T[i]), n(i) {} // Create with size i
~Vector() {delete[] data;} // Return memory
Vector(const Vector<T>& v): data(new T[v.n]), n(v.n) { // Copy constructor
copy(v.begin(), v.end(), begin());
}
Vector& operator=(const Vector& v) { // Assignment
if (&v != this) { // Assignment to self?
delete[] data; // If not, resize and copy
data=new T[n=v.n];
copy(v.begin(), v.end(), begin());
}
return *this; // Allow a=b=c;
}
template <class P> Vector(P b, P e): data(new T[e-b]), n(e-b) { // Templated member
copy(b, e, data); // Initialize from sequence [b, e)
}
};
A type defined in a class is accessed through class::type Vector<int>::iterator p; // Type is int*
Vector<int>::const_iterator cp; // Type is const int*
Member functions may be overloaded on const. Overloaded member functions need not have the same return types. const member functions should not return non-const references or pointers to data members. Vector<int> v(10); // Uses non-const [], begin(), end()
const Vector<int> cv(10); // Uses const [], begin(), end()
cv=v; // Error, non-const operator= called on cv
v[5]=cv[5]; // OK. assigns to int&
cv[5]=v[5]; // Error, assigns to const int&
p=cv.begin(); // Error, would allow *p=x to write into cv
cp=cv.begin(); // OK because can't assign to *cp

Defining Iterators. Sometimes a container's iterator types must be defined as nested classes overloading the usual pointer operations rather than typedef'ed to pointers. In order to work properly with functions defined in <algorithm>, iterators should define the following 5 public typedefs:
iterator_category: one of the following (defined in <iterator>):
output_iterator_tag (if sequential writing is supported)
input_iterator_tag (if sequential reading is supported)
forward_iterator_tag (if both are supported)
bidirectional_iterator_tag (if the iterator can be decremented)
random_access_iterator_tag (if all pointer operations are supported)
value_type: the type of the elements, for example, T
difference_type: the result of iterator subtraction, usually ptrdiff_t (a signed int type)
pointer: the type returned by operator->(), usually T* or const T*
reference: the type returned by operator*(), usually T& or const T&

Operator -> should be overloaded as a unary function returning a pointer to a class to which -> will be applied, i.e. x->m is interpreted as x.operator->()->m. Nested class members are named Outer::Inner::member. Outer and inner classes cannot access each other's private members. Templated members defined outside the class need their own template declarations.

template <class T> class Vector {
public:

// Reverse iterator for Vector, i.e. ++p goes to the previous element.
class reverse_iterator {
private:
T* p; // Points to current element
public:

// typedefs needed to work with <algorithm> functions
typedef std::random_access_iterator_tag iterator_category; // Defined in <iterator>
typedef T value_type; // Type of element
typedef ptrdiff_t difference_type; // Result of iterator subtraction, usually int
typedef T* pointer; // Type returned by operator ->
typedef T& reference; // Type returned by operator *

reverse_iterator(T* a=0): p(a) {} // Implicit conversion from T* and iterator
iterator base() const {return p;} // Convert to normal iterator

// Forward operators
reverse_iterator& operator++() {--p; return *this;} // prefix
reverse_iterator operator++(int); // postfix, we pretend it's binary
reference operator*() const {return *p;}
pointer operator->() const {return p;} // We pretend it's unary
bool operator==(Vector<T>::reverse_iterator b) const {return p==b.p;}
bool operator!=(Vector<T>::reverse_iterator b) const {return p!=b.p;}
// Also, bidirectional and random operators
};
reverse_iterator rbegin() {return end()-1;}
reverse_iterator rend() {return begin()-1;}
// Other members...
};

// Code for postfix ++
template <class T>
inline Vector<T>::reverse_iterator Vector::reverse_iterator::operator++(int dummy) {
Vector<T>::reverse_iterator result = *this;
++*this;
return result;
};

// Print a Vector in reverse order
int main() {
Vector<int> a(10);
for (Vector<int>::reverse_iterator p=a.rbegin(); p!=a.rend(); ++p)
cout << *p << endl;
vector<T> supplies random reverse_iterator and const_reverse_iterator as above. Const iterators would typedef pointer as const T* and reference as const T&.

A static data member is shared by all instances of a class. It must be initialized in a separate declaration, not in the class definition or in the constructor initialization list. A static member function cannot refer to this or any non-static members (and therefore it makes no sense to make them const). Static members may be referenced either as object.member or class::member.
class Counter {
static int count; // Number of Counters that currently exist (private)
public:
static int get() {return count;}
Counter() {++count;}
~Counter() {--count;}
Counter(const Counter& c) {++count;} // Default would be wrong
Counter& operator=(const Counter& c) {return *this;} // Default would be OK
};
int Counter::count = 0; // Initialize here, OK if private
main() {
Counter a, b, c;
cout << b.get(); // 3
cout << Counter::get(); // 3
}
Inheritance
Inheritance is used to write a specialized or enhanced version of another class. For example, an ofstream is a type of ostream. class D: public B defines class D as derived from (subclass of) base class (superclass) B, meaning that D inherits all of B's members, except the constructors, destructor, and assignment operator. The default behavior of these special member functions is to treat the base class as a data member. class String: public Vector<char> {
public:
String(const char* s=""): Vector<char>(strlen(s)) {
copy(s, s+strlen(s), begin()); // Inherits Vector<char>::begin()
}
};
String a="hello"; // Calls Vector<char>::Vector(5);
a.size(); // 5, inherits Vector<char>::size()
a[0]='j'; // "jello", inherits Vector<char>::operator[]
String b=a; // Default copy constructor uses Vector's copy constructor on base part
b=a; // Default = calls Vector's assignment operator on base part
The default destructor String::~String() {} is correct, since in the process of destroying a String, the base is also destroyed, calling Vector<char>::~Vector() {delete data[];}. Since there is no need to write a destructor, there is no need to redefine copying or assignment either.
Although String inherits Vector<char>::data, it is private and inaccessible. A protected member is accessible to derived classes but private elsewhere.
class B {
protected:
int x;
} b; // Declare class B and object b
b.x=1; // Error, x is protected

class D: public B {
void f() {x=1;} // OK
};
By default, a base class is private, making all inherited members private. Private base classes are rare and typically used as implementations rather than specializations (A string is a vector, but a stack is not). class Stack: Vector<int> { // or class Stack: private Vector<int>
public:
bool empty() const {return size()==0;} // OK
} s;
s.size(); // Error, private
s.empty(); // OK, public

A class may have more than one base class (called multiple inheritance). If both bases are in turn derived from a third base, then we derive from this root class using virtual to avoid inheriting its members twice further on. Any indirectly derived class treats the virtual root as a direct base class in the constructor initialization list.
class ios {...}; // good(), binary, ...
class fstreambase: public virtual ios {...}; // open(), close(), ...
class istream: public virtual ios {...}; // get(), operator>>(), ...
class ifstream: public fstreambase, public istream { // Only 1 copy of ios
ifstream(): fstreambase(), istream(), ios() {...} // Normally ios() would be omitted

No comments:

Post a Comment