C++ Project Organization Guidelines

S

Steven T. Hatton

I think Danny was one cup of coffee shy of full consciousness when he wrote
this, but the gist of it makes sens to me:

"C++ Project Organization Guidelines
Last updated May 26, 2005.
http://www.informit.com/guides/content.asp?g=cplusplus&seqNum=175
Last week's article about inline functions subtly brought into the limelight
another important issue, namely how to organize the files of a typical C++
program, or project. This week I discuss this issue and answer the
following questions: How to decide which code components should be included
in a header file and which ones belong to a source file? How many files
should be used in a project have[sic]?"

....

"Poorly-organized projects seldom succeed. Proper organization of project
units, consistent and intelligible file naming conventions and a reliable
configuration management tool (a topic which I didn't discuss here) are
prerequisites for a project's successful completion. Of course, these
measures can't replace talented and experienced developers and meticulous
design. Yet, unlike the latter, project organization is rarely discussed in
programming books."


//----------------------------------------------------------------------
This is the kind of discussion I was hoping to find in Sutter and
Alexanderescu's _Coding Standards_, but didn't. I believe C++ programmers
tend to avoid this kind of discussion because it's too much like politics
and religion. I also believe it's a topic overdue for serious discussion.

I actually don't believe he went nearly far enough. For example, I believe
it is advisable to organize a project into namespaces with file locations
reflecting the namespace hierarchy, and file names reflecting (in general)
class names.

Here's one possible way to arrange a project which I find attractive.
Tue Jul 26 00:23:21:> find . -type d | grep -v /.svn
..
../bin
../doc
../lib
../src
../src/diagram
../src/mtree
../src/osgQt
../src/patControl
../src/rstest
../src/osgqttest
../src/widgets
../build
../build/diagram
../build/mtree
../build/osgQt
../build/patControl
../build/rstest
../build/osgqttest
../build/widgets
../include
../include/math
../include/util
../include/control
../include/diagram
../include/mtree
../include/osgQt
../include/patControl
../include/rstest
../include/osgqttest
../include/widgets

The advantage to splitting src and include into too parallel hierarchies is
that it helps ensure the dependencies are actually separated. Each
namespace has its own build directory which produces its own libraries and
or executables. The executables are placed in ./bin and the libraries are
placed in ./lib. I try to link against the libraries in ./lib rather than
the ones in the build tree because that treats them as they would be
treated in an installed environment.

The listing above has the following namespaces:
control
diagram
math
mtree
osgQt
osgqttest
patControl
rstest
util
widgets

Typically, I create one unit (see Danny's article) per class, and give the
files a name identical to the class name except that the file names have
extensions. The header guards typically (ideally) are of the form
_NAMESPACE_CLASSNAME_H_. This is one place where the Cpp can hold a subtle
gotcha. Suppose you just use the file name as the foundation for the
header guard, and have two files with the same name in different parts of
the project. Another problem arrises when I rename a class, and thence its
filename. If I forget to rename the header guard, and subsequently create
another class of the same name, I can end up excluding one of the two
before it is processed for the first time.

Sometimes I find myself creating namespace local functions, or a bunch of
tiny classes. When that happens, I try to put them all in one unit with
filenames corresponding to the namespace name.

There are probably many refinements I can still make on my project
organization strategies. For one, I need to strengthen the concept of
interface, and the separation between interface and implementation. This
applies to both classes, and to namespaces.

See for example, the Xerces project:
http://svn.apache.org/viewcvs.cgi/xerces/c/trunk/src/xercesc/dom/

Note that they follow very closely the recommendations of Stroustrup
regarding the use of pure interfaces (ABCs). Ironically, this seems to
have originated with the Java implementation of Xerces, and was carried
over when (as I understand things) Xerces was ported to C++. Another point
worth mentioning is the use of #include <xercesc/dom/DOMAttr.hpp>, etc.,
which I would like to tell you reflects the namespace hierarchy (as it
would in my code), but, unfortunately, they use a #define to bracket their
namespaces, and I don't recall what that is defined as, nor where it's
defined.

Another point is that I believe it would be a good idea for C++ programmers
to try to agree on a file name extension convention. I personally
dislike .cpp because it's reminiscent of the C preprocessor, but I can get
over that. .h is problematic because there are subtle differences between
C and C++, and .h can be understood by a multilingual tool to mean a C
header file. I therefore favor .cpp and .hpp. Unfortunately, KDevelop
doesn't currently (correctly) support that option.

I have one critique of Danny's code. I don't believe he needs the
log.close() in the destructor:

// logfile.h
#include <fstream>
#include <string>
class Logfile
{
public: //member functions are declared but not defined:
Logfile(const std::string & path);
~Logfile();
//...
private:
std::eek:fstream log;
std::string name;
};

// logfile.cpp
#include "logfile.h"
//definitions of functions declared in the .h file
Logfile::Logfile(const std::string & path) :name(path)
{
log.open(name.c_str());
bool success=log;
//..
}
Logfile::~Logfile()
{
log.close();
//..
}

Another suggestion some people might make is to use a pointer in the header
file so that there is no memory allocation required if it is simply
#included in another translation unit. In that case he would need a
destructor. I'm not sure of the ramifications of using an auto_ptr in the
class definition. Would that be superior to using a local data member or a
pointer to std::eek:fstream?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top