Practical C++ Programming Tips

or, "Just how pedantic can he get?"


(image: kde)

Table of Contents

  1. Use the assert() macro liberally
  2. Use const wherever you can
  3. Do you really need dynamic_cast?
  4. Methods overriding virtuals should be declared virtual
  5. Incomplete declarations: faster builds, fewer dependencies
  6. Compile with warnings enabled
  7. Don't reduce the visibility of overridden members
  8. Rid yourself of those "unused parameter" warnings

Use the assert() macro liberally

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.

top


Use const wherever you can

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.

top


Do you really need dynamic_cast?

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.

top


Methods overriding virtuals should be declared virtual

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 );
	

top


Incomplete declarations: faster builds, fewer dependencies

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".

top


Compile with warnings enabled

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

top


Don't reduce the visibility of overridden members

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.

top


Rid yourself of those "unused parameter" warnings

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.

top


back

Written and maintained by: Sirtaj S. Kang (taj@kde.org)
Last modified: Mon Jan 24 15:00:48 EST 2000