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 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.
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...
Components in a class are usually not visible from the outside. The scope can be set with the following keywords:
structs. Only methods are recommended to be public (i.e., not variables), but anything can be.
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.
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.
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.
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 */
/* ... */
}
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.
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.
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.
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 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 */
}
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.
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.
These functions are like malloc() and free(), but when applied to objects,
they also call constructors and destructors.
Visitor comments
2010-11-29
2011-01-05
2011-10-15
2011-10-15
a is also 10
v is now 30
2011-10-15
2011-10-15
2011-10-15
-http://www.imagineit.com
2011-10-15
2011-10-15
2011-10-15
2011-10-15
2011-10-15
2011-10-15
2011-10-16
2011-10-16
Does newer C++ have optional garbage collecting? The operator+ you define returns a string, which is super handy, but it's never free()'d. Or if you return an actual C++ string, is it free()'d when it goes out of scope or must that be explicit?
2011-10-16
2011-10-16
2011-10-16
2011-10-17
2011-10-17
"A function defined inside a struct is called a method"
No it's not. It's a member-function. This is not Java.
"These functions are like malloc() and free(), but when applied to objects, they also call constructors and destructors."
They do a lot more than that. They initialize the vtables required for polymorphism, create the base classes etc.
Never, ever use malloc() and free() with polymorphic classes.
2011-10-17
2011-10-17
2011-10-17
2011-10-17
2011-10-17
2011-10-17
2011-10-18
2011-10-18
I like the to-the-point mentality of your article and how it sums up the most prominent differences, but I couldn't help noticing A few things:
1. The class rect_prism is a very bad example of object design. It shouldn't inherit from rectangle, instead it should have a rectangle as an attribute. The simplified rule is to decide whether a relation expresses 'is a' or 'has a'. The former suggests inheritance, the latter composition.
The class square is just fine in that respect: a square 'is a' (specialized) rectangle. Since this article isn't about object design, you might just kick the prism. Or you leave it as an example of bad design, to show the reader when to use inheritance, and when not.
2. Your example of operator overloading is explicitely forbidden! You can only overload operators that have at least one user-defined parameter. Pointers are not user-defined! (consider the consequences if you could define an operator+(char*,int) : it would silently break a lot of code using pointer arithmetic!)
3. Regarding references: your example may compile on older compilers, but not those adhering to the newest standard! Local variables of type reference do have to be const! This is a subtle difference to function parameters that are passed by reference.
4. Since you mention new and delete, please do mention new[] and delete[] as well! This is a common area of misunderstanding and source of many memory errors and leaks!
There may be better and more detailled guides, but none that I know can be digested in about half an hour - and that is in my experience the biggest hurdle to get someone started! Thanks for your effort.
2011-10-18
So, while the author didn't quite give the exact definition of the term 'method', he didn't say anything wrong either. :-)
2011-10-18
And, yes, my operator overloading example and other ones are awful and not very orthodox, but they are what they are: examples for a first, air-view of C++ for people accustomed to C. They are not examples of good C++ practice (though they may inspire otherwise bad practices, by the way, as some readers complained).
Your comments are very valuable. Thank you.
2011-10-18
I've learned C++ at a time when there were no compilers for templates yet, and those that existed had a precompiler that translated C++ classes into C code. Terms like 'method', 'getter' or 'setter' weren't coined yet, much less the terminology of modern object design, such as the common design patterns by Gamma et al. I've found that it was sometimes incredibly hard for me to understand young people who've learned all of this at university - not because I wouldn't know of any of the things they talk about, just because I was not familiar with the terminology!
It was frightening at times, because I thought they've learned stuff that I should know about, when in truth it was just a basic methodology I've been using for decades! I've gotten better after I started looking up these terms. Wikipedia is your friend!
Maybe that is also something you cannot stress enough: learn the terminology! Nothing is more frustrating than the feeling you have no inkling about what others are talking about, when in truth you just don't understand a few terms.
2011-10-18
2011-10-18
2011-10-18
2011-10-18
2011-10-20
2011-10-24
2011-12-07
This article was exactly what I needed (as an experienced C programmer) to put a C wrapper around a C++ library that I unfortunately needed to use.
Thank you.