class shape { private: int x_pos; int y_pos; int color; public: shape () : x_pos(0), y_pos(0), color(1) {} shape (int x, int y, int c = 1) : x_pos(x), y_pos(y), color(c) {} shape (const shape& s) : x_pos(s.x_pos), y_pos(s.y_pos), color(s.color) {} ~shape () {} shape& operator= (const shape& s) { x_pos = s.x_pos, y_pos = s.y_pos, color = s.color; return *this; } int get_x_pos () { return x_pos; } int get_y_pos () { return y_pos; } int get_color () { return color; } void set_x_pos (int x) { x_pos = x; } void set_y_pos (int y) { y_pos = y; } void set_color (int c) { color = c; } virtual void DrawShape () {} friend ostream& operator<< (ostream& os, const shape& s); }; ostream& operator<< (ostream& os, const shape& s) { os << "shape: (" << s.x_pos << "," << s.y_pos << "," << s.color << ")"; return os; }
Examining the C++ class "shape". The keyword class begins the definition of the user-defined type. The keyword private means that the names x_pos, y_pos and color can only be used by member functions (which are functions defined inside the class definition). The keyword public starts the public-section, which constitutes the interface to objects of the class, that means, names and member functions in this section can be accessed by the user of the object. Because of the attributes being private, the class has public member functions to get and set the appropriate values. These member functions belong to the interface.
Note that a class is abstract, whereas the instantiation of a class leads to an object, which can be used and modified:
shape MyShape (12, 10, 4); int color = MyShape.get_color(); shape NewShape = MyShape;
where shape is the class name and MyClass is an object of the class shape.
shape () : x_pos(0), y_pos(0), color(1) {}
is the default constructor - the constructor without arguments. A constructor builds and initializes an object, and there are more possible kinds of constructors:
shape (int x, int y, int c = 1) : x_pos(x), y_pos(y), color(c) {}>
This is a constructor with three arguments where the third one is a default argument:
shape MyShape (10, 10);
results in: x_pos == 10, y_pos == 10, color == 1.
shape (const shape& s) : x_pos(s.x_pos), y_pos(s.y_pos), color(s.color) {}
This is an important constructor, the so-called copy-constructor. It is called when you write code like this:
shape MyShape; shape NewShape (MyShape);
After that, MyShape and NewShape have the same attributes, the object NewShape is copied from the object MyShape using the copy constructor.
Note the argument const shape& s. The & means "reference to", when a function call takes place, the shape is not copied onto the stack, but only a reference (pointer) to it. This is important, when the object given as argument is huge, because then copying would be very inefficient.
~shape () {}
is the destructor. It is called, when an object is destroyed - for example when it goes out of scope. The shape destructor has nothing to do, because inside the shape class no dynamically allocated memory is used.
shape& operator= (const shape& s) { x_pos = s.x_pos, y_pos = s.y_pos, color = s.color; return *this; }
Operator overloading. In C++ it is possible to overload operators - that is to give them a new meaning or functionality. There is a set of operators which can be defined as member functions inside a class. Among these the assignment operator can be found, which is used when writing the following code:
shape MyShape, NewShape; NewShape = MyShape;
Note that the operator= is called for the left object, i.e. NewShape, so there must be only one argument in the declaration. This is true for all other C++ operators as well.
When a member function is called, the system automatically adds the this-pointer to the argument list. The this-pointer points to the object, for which the member function is called. By writing return *this, the concatenation of assignment gets possible:
shape OldShape, MyShape, NewShape; NewShape = MyShape = OldShape; int get_x_pos () { return x_pos; }
gives you the value of x_pos. An explicit interface function is necessary, because private mebers cannot be accessed from outside the object.
virtual void DrawShape () {}
declarates a function with no argument that draws the shape. Because a shape is abstract and we have no idea of what it looks like precisely, there's no implementation for DrawShape. The keyword virtual means that this member function can be overwritten in a derived class (see [1], §6). For example, a class dot could be derived from shape. DrawShape then would be overwritten to draw the dot at the position (x_pos,y_pos) and with the colour color.
Put-to operator. Now consider the definition of the operator<<:
ostream& operator<< (ostream& os, const shape& s) { os << "shape: (" << s.x_pos <<"," << s.y_pos << "," <<s.color << ")"; return os; }
The usual way in C++ to display information on the screen is to write:
cout << "Hello, World!";
With the upper code we overload the put-to-operator (operator<<) to be able to send shapes directly to an output stream:
shape MyShape (5, 9); cout << MyShape;
shows on the output screen: shape: (5,9,1)
friend ostream& operator<< (ostream& os, const shape& s);
Friend and inline. The keyword friend in front of a function declaration means that this function has access to the private members of the class, where the declaration takes place. You can see that x_pos, y_pos and color are used directly by operator<<. It's also possible to define a whole class as friend class.
Note that all member functions of shape are defined inside the class declaration. If so, the member functions are all "inline". Inline means, that whereever the function is called, the compiler creates no function call but inserts the code directly to decrease overhead.
To inline a member function defined outside the class the keyword inline must be used:
inline int shape::get_x_pos () { return x_pos; }
Nice Classes. For STL it's wise to create classes that meet the requirements of Nice Classes. For example, Borland C++ expects an object to be stored in a container to have an assignment operator defined. Additionally, if a container holds its objects in a particular order, a operator like the operator< must be defined (the latter to fix a half-order).
A class T is called nice iff it supports:
such that:
A member function T::s(...) is called equality preserving iff
a == b implies a.s (...) == b.s (...)
A class is called Extra-Nice iff
all of its member functions are equality preserving
The theory of Nice Classes origins from a joint work between HP and Andrew Koenig from the Bell Labs.