- Use the
assert() macro liberally
- Use
const wherever you can
- Do you really need
dynamic_cast?
- Methods overriding virtuals should be declared virtual
- Incomplete declarations: faster builds, fewer dependencies
- Compile with warnings enabled
- Don't reduce the visibility of overridden members
- Rid yourself of those "unused parameter" warnings
The assert() macro should be used to test for conditions
that should not occur in your code. This makes it much easier to find
misbehaving bits of code during testing, before they become a problem.
#include<assert.h>
double sqrt( double val )
{
// since we are calculating square root, val shoul
// always be positive.
assert( val >= 0.0 );
// ... calculate and return sqrt ...
}
See the assert man page in section 3 of the manual for
further information.
Examine the following code fragment:
// in myclass.h
class MyClass
{
private:
int myvalue;
public:
void setValue( int val );
void copyValue( const MyClass& other );
int value() const;
};
// in myclass.cpp
void MyClass::setValue( int val )
{
myvalue = val;
}
void MyClass::copyValue( const MyClass& other )
{
myvalue = other.value();
}
int MyClass::value() const
{
return myvalue;
}
In the above class, the value() method is not intended to alter the class
itself, so it has been declared const.
-
A method declared
const is not allowed to change any
member variables in the class. Nor is it allowed to call any other
non-const methods of the class. eg, MyClass::value() above.
-
If a
const pointer or reference to an object is
passed as a parameter to a method, the method is not allowed to
modify the object. In the case of classes, it is not allowed to call any
non-const members of the class nor modify any of the member variables
to which it has access. eg MyClass::copyValue() above.
Using this has several advantages:
-
To users of the class, it is immediately obvious that the const methods will
not modify the object.
-
Many accidental modifications of objects will be caught at compile time.
-
Compilers like const since it allows them to do better optimization.
Currently, many people building KDE with egcs use the -fno-rtti flag
since it results in significantly smaller object files and executables.
Since dynamic_cast uses RTTI, you should think long and hard
about whether you really need to use it in your code, until people are
more comfortable with building KDE with RTTI enabled.
When writing a new class that overrides virtual methods in the base class,
it is a good idea to also mark the overriding method as virtual in the
class definition. This makes it immediately obvious to users of your class.
// should
protected:
virtual void paintEvent( QPaintEvent *ev );
// shouldn't
protected:
void paintEvent( QPaintEvent *ev );
Examine the following code fragment:
// fragment from myclass.h
#include"someclass.h" // defines class SomeClass
class myclass {
SomeClass *getptr();
private:
SomeClass *ptr;
};
In the above, rather contrived bit of code, MyClass only ever refers
to a pointer to SomeClass without ever dereferencing it. Yet, if
someclass.h is modified, make will recompile every source
file that includes myclass.h! This is suboptimal and
doesn't need to happen. Incomplete declarations will help you improve
the situation.
// fragment from nicer myclass.h
class SomeClass;
class MyClass {
SomeClass *getPtr();
private:
SomeClass *ptr;
};
We have replaced the #include directive with an incomplete declaration
for SomeClass. As long as we only ever refer to references
or pointers to SomeClass without ever dereferencing them
(eg invoking a SomeClass method), we don't need to include someclass.h.
There are two advantages: Firstly, you can change SomeClass as much as you
like, and sources that include myclass.h don't need to be recompiled
(unless they include someclass.h, of course).
Secondly, compilation of sources including myclass.h will be faster
since you have saved the reading and parsing of someclass.h.
Note that your code will have to include someclass.h wherever it needs to
know more about the composition of SomeClass, eg the classes
from which it is derived, its size, or its methods and member variables.
As an aside, this is a good reason to use pointers to objects as member
variables, ie "SomeClass *ptr" rather than "SomeClass
ptr".
Always compile your sources with warnings enabled.
Besides the standard
warning flags -W -Wall, you should also use the
-ansi -pedantic flags. This is helpful in many ways:
- ANSI compliance and portability problems will be caught much
earlier, reducing the load on both you and the porting teams.
- Once you get rid of the trivial warnings, you won't miss
any warnings that you really should be looking out for.
- If your code ends up in the libraries (a common occurence for
widgets), programmers who use your classes won't have to put up
with warnings in your code if they are more stringent about
warning-less compilation than you.
Other useful warning flags include -Wtraditional -Wpointer-arith
-Wcast-qual -Wcast-align -Wstrict-prototypes -Woverloaded-virtual and
-Weffc++ (egcs only). Note that many of
these flags are enabled by egcs by default.
If you are using gcc or egcs, don't assume that everyone who compiles your
code will be using it too! On many platforms, gcc and egcs are either
not available or not optimal. Get rid of warnings generated by these flags
and you will have dealt with many common problems when porting to other
compilers.
To set compilation flags easily, define the CXXFLAGS environment
variable before you run configure. Your environment variable
setting will override the default.
An example (using tcsh):
zero ssk ~/build> setenv CXXFLAGS "-W -Wall -ansi -pedantic -Wtraditional \
-Wpointer-arith -Wcast-qual -Wcast-align -Wstrict-prototypes \
-Woverloaded-virtual"
zero ssk ~/build> ./configure
Unless you have a very good reason, you should not reduce the
visibility of an overridden virtual method.
class MyBase
{
protected:
virtual void doIt();
...
};
// DON'T
class BadChild : public MyBase
{
private:
virtual void doIt();
};
// DO
class GoodChild : public MyBase
{
protected: // <-- note visibility is the same as MyBase
virtual void doIt();
};
Hiding virtuals results in making it difficult or even impossible
to change the behaviour of a class by inheritance, driving hapless
programmers to hacks like #define private public (if you
hadn't seen that one before, please forget you heard it from me).
If you have a good reason for derived classes to not override your
reimplemented method, say so clearly in the class documentation (your
class is documented, right?). Hiding virtuals breaks the is-a
relationship of classes.
When implementing a function that receives a parameter you do
not want to use, skip the name of the parameter in the declaration and the
compiler won't generate an unused parameter warning. For example, in
this function
void KornButton::mouseDoubleClickEvent( QMouseEvent * ) {
emit doubleClick();
}
the QMouseEvent * parameter is unused, so I've left out
it's name.
|