2010-10-26 Ángel Ortega

So you were a great system programmer, back in the old days; your low level programs were celebrated by clever people, you loved C pointers and some days even considered Assembler as an option. You were happy and self-confident. But somehow you screwed something, got trapped in a time vortex and you ended today, trying to maintain or develop a program using that pesky Object Oriented Programming model in something called C++. I understand you; follow this guide and learn a bunch of things that will put you out of your misery and understand this brave new world.

Structs

Structs are just like in C; you know, use and love that little things. You have them in C++, with a little advantage: they can contain functions.

struct rectangle { int x; int y; int width; int height;

int surface(void) { return width * height; } };

Be honest and say that you always wanted that. And another thing you always desired: the struct keyword is not needed in the declaration:

int main(int argc, char *argv[]) { rectangle r; int s;

r.x = 0; r.y = 0; r.width = 100; r.height = 50;

s = r.surface(); }

A function defined inside a struct is called a method. Take note that it can access struct members directly.

But you, the clever and experienced C programmer, will had never defined the struct that way; you are used to have your structs in a header file, and your code in a pure source file. Well, do it:

/* in a .h file */ struct rectangle { int x; int y; int width; int height;

/* just the prototype here */ int surface(void); };

/* in a .cpp file */ int rectangle::surface(void) { return width * height; }

You are ready to understand classes.

Classes

Yes, classes are those things young programmers are all talking about. But they are nothing more than a special kind of structs , where you must say how visible their components are to the outside world. So, you can write the above code this way:

/* in a .h file */ class rectangle { public: int x; int y; int width; int height;

/* just the prototype here */ int surface(void); };

/* in a .cpp file */ int rectangle::surface(void) { return width * height; }

/* in your main file */ int main(int argc, char *argv[]) { rectangle r; int s;

r.x = 0; r.y = 0; r.width = 100; r.height = 50;

s = r.surface(); }

Few things have changed; the keyword was changed from struct to class , and a new public: keyword was introduced. This leads us to...

Encapsulation

Components in a class are usually not visible from the outside. The scope can be set with the following keywords:

public All components under this keyword can directly accessed from anywhere. This is the same as in old world structs . Only methods are recommended to be public (i.e., not variables), but anything can be. private All components under this keywords are accesible only by methods in its own class. protected All components under this keywords are accesible only to methods in its own and derived classes. We'll talk about that later.

A class variable can be static ; this means that the same copy of that variable will be shared among all components of a class. Useful to implement object counters and such. It must be initialized from outside the class.

Regarding private or protected variables, you'll hear those strange people talking about 'getters/setters' or even 'accessors/mutators': don't panic, those are just methods to set and get values from those hidden variables. You always did the same in object files.

Constructors and destructors

Wouldn't it be good if you could create a rectangle instance this way:

rectangle r (10, 10, 40, 50);

Well, you can; you just have to define a constructor, that is, a method that will be called when the instance is constructed. You just name it after the class:

rectangle::rectangle(int a, int b, int w, int h) { x = a; y = b; width = w; height = h; }

Other initialization can be done there as well. Remember that you must include the constructor prototype in your class.

The opposite is the destructor; a method called when the object is destroyed. Usually it's not needed, but you can release resources in it, as open files and such. Just name it prepending a tilde to the class name.

rectangle::~rectangle() { fclose(log_file); }

It's common practice, if a count of objects is needed, to increase a static class variable from the constructor and decrease it from the destructor.

This

When you saw the previous declaration of rectangle() , you didn't like to have different names for arguments and variables. Though you find useful to be able to access class variables directly, you would like to have a way to distinguish those variables from others, as for example method arguments. You can do it with this :

rectangle::rectangle(int x, int y, int width, int height) { this->x = x; this->y = y; this->width = width; this->height = height; }

This magical pointer always points to the current object.

Inheritance

It's possible to create classes that share features from another, possibly overwriting / adding new attributes and methods to the original one. This is called inheritance. Classes that inherit from others are called derived.

Let's create a class for a rectangular prism:

class rect_prism : public rectangle { public: int z; int depth;

rect_prism(int, int, int, int, int, int); };

/* constructor */ rect_prism::rect_prism (int a, int b, int c, int w, int h, int d) { x = a; y = b; z = c; width = w; height = h; depth = d; }

So you can create rect_prism objects, and as they inherit from the rectangle class, they can use the surface() method.

A better way of writing the constructor is using also inheritance:

/* constructor */ rect_prism::rect_prism (int a, int b, int c, int w, int h, int d) : rectangle(a, b, w, h) { z = c; depth = d; }

So rectangle() is called with the appropriate arguments, and then the rest of the arguments assigned.

You can also create a square class as a special kind of rectangle:

class square : public rectangle { public: square(int, int, int); };

/* constructor */ square::square (int a, int b, int wh) : rectangle(a, b, wh, wh) { }

This way of calling a function from the base class is useful, but it can only be used in constructors and is always executed before any other code. But you can call a method from the base class at any time by explicitly naming the class:

int derived_class::do_something(int value1, int value2) { /* do things not related to the base class */ this->value2 = value2;

/* then call the base method */ base_class::do_something(value1);

/* more code, if needed */ /* ... */ }

Virtual methods

And what if you want to call a method from a base class function that can be redefined (or not) in a derived class? You just add virtual to its definition. That virtuality means that calls are not hardcoded inside the binary file, but an object indirection and resolving is done on every call. As you imagine this adds a little space and execution time overhead, but who cares.

Overloading

Function and method overloading

You can use the same name for a set of functions if they vary in the number or type of their arguments:

double absolute(double x) /* double floating point version */ { return fabs(x); }

int absolute(int x) /* integer version */ { return x > 0 ? x : -x; }

The compiler will construct the correct call for any use. I know you'll love this.

Operator overloading

The operators can also be overloaded. For example, you can overload the + (plus) sign to mean concatenation for strings):

char * operator + (char *one, char *two) { char *r = malloc(strlen(one) + strlen(two) + 1); strcpy(r, one); strcat(r, two);

return r; }

This function returns a newly allocated string that is the concatenation of the two ones sent as arguments.

char *full = "Hello " + "There!";

This sounds cool, but it's wise to handle with care.

References

It's now possible to declare a reference to variable. This means that everytime one of the variables change, the other do as well, as they really point to the same storage.

{ int a = 5; int &b = a;

b = 10; /* a is also 10 */ }

A reference cannot be changed to point to another variable afterwards: they are constant.

Yes, all this can be done with pointers. I find it particularly confusing, but you'll need to know what is about when you see that funny & there.

It can also be used for function arguments so they are real pass-by-reference.

void add_to_me(int &a, int b) { a += b; }

int v = 10; add_to_me(v, 20); /* v is now 30 */

Yes, this can also be done with preprocessor macros, but here you have all type checking and such.

Exceptions

Exceptions can be seen as sophisticated goto/switch control structures. They are implemented by using the try / throw / catch construction.

try { if (some_condition) { /* do important things */ throw 1; } if (another_condition) { /* do another set of important things */ throw 2; }

throw 0; } catch (int r) { /* do something amazing with the result */ }

Templates

I'm sure at least once in your long programmer life you found yourself implementing two or more different versions of a function that does the same but for different types of arguments (e.g. some for integers, another one for floats). You probably ended up writing clever and cumbersome preprocessor macros to avoid having copies of the same algorithm.

Templates will help you no longer feeling miserable.

See how I implement a multiply function for any kind of argument:

template <class ttype> ttype multiply(ttype a, ttype b) { return a * b; }

Everytime a call to multiply() is written in your code, a special version of the function is compiled in.

Other features

Default values for function arguments

C++ allows for default arguments to be defined:

int sum(int a = 0, int b = 0) { return a + b; }

so sum() can be called with two, one or no arguments and always work.

New and delete

These functions are like malloc() and free() , but when applied to objects, they also call constructors and destructors.

See also

Angel Ortega - angel@triptico.com