This is a short list of recommendations on how to use C++. My experiences are from using gcc 2.8.0 and Visual C++ 6.0. I had to have things compatible between these two compilers, and between Unix and Windows.
Contents
IO of binary files
When are destructors called for local variables
Use {} to keep things local
Scope of variables declared in for()
When to use virtual members
IO of binary files
To make sure that there is no CR/LF translation on non-Unix computers, you have to use the following lines to open streams to files with binary data.
ofstream os("output.flt", ios::out | ios::binary);
ifstream is("output.flt", ios::in | ios::binary);
For Visual C++, when using fstream.h, use in addition the flag ios::nocreate. Otherwise you can open a non-existing file for reading, without complaining. (This is not necessary when using fstream).
When are destructors called for local variables
Non-static (or 'automatic' ) variables are 'destructed' automatically when they go out of scope. Scope is a farily complicated thing, and I'm not going to repeat the definition here. Roughly speaking the scope ends when you encounter the } around the declaration of the variable. See also the use of {} and how scope is defined in the for() statement.
Variables are destructed (by the compiler) by calling the appropriate destructor of their class. If the objects allocate memory (and hence the destructor should free that memory), this means that you recover the memory allocated.
class array
{
private:
float *ptr;
public:
// constructor
array(int n) { ptr = new float[n]; }
// destructor
~array() { delete [] ptr; }
}
main()
{
// ...
{
array a(5); // allocates memory
// do something
}
// here the array is destructed, and so the memory is freed
//....
}
Use {} to keep things local
Use of the grouping construct {} enables you to declare variables local to that group. When leaving the group, all local variables are destructed. This has the advantage that the reader of the code knows (s)he shouldn't worry about these variables to understand the rest of the code.
In a way this can be understood as if every use of {} is like a function call (with local variables declared in the function). Of course, you don't have the overhead of stack manipulations and jumps involved in a proper function call.
// recommended usage[indent]
void f(int a)
{
if(a==1)
{
myclassA Aobject;
// here I do something with 'Aobject', and maybe 'b'
}
// Aobject does not exist here anymore
}
[/indent]This tip is just an extension of the 'avoid global variables' credo.
As always, this can be disabused as in the following piece of code, where the outer variable 'a' is hidden by a local 'a', resulting in not very readable code.
// not very readable code[indent]
{
int a=1;
{
// local variable hides outer 'a'
int a;
a = 2;
assert (a==2);
}
// a is again the previous variable
assert (a==1);
}
[/indent]Scope of variables declared in for()
The new ANSI C++ standard specifies that variables declared as in for(int i=1; ...) have a scope local to the for statement. Unfortunately, older compilers (for instance Visual C++ 5.0) use the older concept that the scope is the enclosing group. Below I list 2 possible problems, and their recommended solutions:
- you want to use the variable after the for() statement
you have to declare the variable outside of the for() statement.
int i;
for(i=1; i<5; i++)
{ /* do something */ }
if (i==5) ...
- you want to have multiple for() loops with the same variables.
Put the for statement in its own group. You could also declare the variable outside of the 'for', but it makes it slightly trickier for an optimising compiler (and a human) to know what you intend.
{
for(i=1; i<5; i++)
{ /* do something */ }
}
When to use virtual members
Make a member 'virtual' if a derived class extends the functionality of a member of the base class, and this extended functionality has to be accessible:
- inside other member functions of the base class
- when using pointers that can point to either an object of the base class, or an object of the derived class.
Example: multi-dimensional arrays which are defined recursively in terms of a 1D array.
We wanted to have a 'grow' member that enlarged the outer dimension of the multidimensional array. At first sight, this is simply calling a general grow of the base class. However, 'grow' has to know the size of the new elements (which are again multidimensional arrays). So, we had to define in the derived class a new 'grow', which calls the base class 'grow' first, and then does more stuff.
At many points in the base class, 'grow' is called to adjust sizes. By making 'grow' virtual we avoid to having to rewrite these members for the derived class.
Caveat:
For members of the base class which use temporary objects of its own type, the base class 'grow' will be called. For instance:
class array
{
...
virtual void grow(int new_size);
array& operator +=( const array& a)
{ /* some definition using 'grow' */ }
array operator +(const array& a1, const array& a2)
{
array a=a1;
a += a2;
// Warning, this will call array::grow, even if a1 is really from a derived type
}
};
Thus, you should provide a member of the derived class for every member of the base class which uses temporary objects.
[indent]class multiarray : public array
{
...
virtual void grow(int new_size);
multiarray operator +(const multiarray& a1, const multiarray& a2)
{
multiarray a=a1;
a += a2;
}
};
[/indent]