C++ Project Organization Guidelines

Discussion in 'C++' started by Steven T. Hatton, Jul 26, 2005.

  1. 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?
    --
    If our hypothesis is about anything and not about some one or more
    particular things, then our deductions constitute mathematics. Thus
    mathematics may be defined as the subject in which we never know what we
    are talking about, nor whether what we are saying is true.-Bertrand Russell
     
    Steven T. Hatton, Jul 26, 2005
    #1
    1. Advertising

  2. EventHelix.com, Jul 26, 2005
    #2
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    0
    Views:
    411
  2. Martin Unsal

    Project organization and import

    Martin Unsal, Mar 5, 2007, in forum: Python
    Replies:
    51
    Views:
    1,147
  3. Hamilton, William

    Project organization and import redux

    Hamilton, William, Apr 5, 2007, in forum: Python
    Replies:
    0
    Views:
    272
    Hamilton, William
    Apr 5, 2007
  4. bramble
    Replies:
    0
    Views:
    298
    bramble
    Nov 22, 2007
  5. LB
    Replies:
    5
    Views:
    1,051
Loading...

Share This Page