Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to overcome the namespace evil of C++ header files?

With one of my projects I will head into the C++ field. Basically I am coming from a Java background and was wondering how the concept of Java packages is realized in the C++ world. This led me to the C++ concept of namespaces.

I am absolutely fine with namespaces so far but when it comes to header files things are becoming kind of inefficient with respect to fully qualified class names, using-directives and using-declarations.

A very good description of the issue is this article by Herb Sutter.

As I understand it this all boils down to: If you write a header file always use fully qualified type names to refer to types from other namespaces.

This is almost unacceptable. As a C++ header commonly provides the declaration of a class, a maximum of readability has top priority. Fully qualifying each type from a different namespace creates a lot of visual noise, finally diminishing readability of the header to a degree which raises the question whether to use namespaces at all.

Nevertheless I want to take advantage of C++ namespaces and so put some thought into the question: How to overcome the namespace evil of C++ header files? After some research I think typedefs could be a valid cure to this problem.

Following you will find a C++ sample program which demonstrates how I would like to use public class scoped typedefs to import types from other namespaces. The program is syntactically correct and compiles fine on MinGW W64. So far so good, but I am not sure whether this approach happily removes the using keyword from the header but brings in another problem which I am simply not aware of. Just something tricky like the things described by Herb Sutter.

That is I kindly ask everybody who has a thorough understanding of C++ to review the code below and let me know whether this should work or not. Thanks for your thoughts.

MyFirstClass.hpp

#ifndef MYFIRSTCLASS_HPP_
#define MYFIRSTCLASS_HPP_

namespace com {
namespace company {
namespace package1 {

class MyFirstClass
{
public:
    MyFirstClass();
    ~MyFirstClass();

private:

};

} // namespace package1
} // namespace company
} // namespace com

#endif /* MYFIRSTCLASS_HPP_ */

MyFirstClass.cpp

#include "MyFirstClass.hpp"

using com::company::package1::MyFirstClass;

MyFirstClass::MyFirstClass()
{
}

MyFirstClass::~MyFirstClass()
{
}

MySecondClass.hpp

#ifndef MYSECONDCLASS_HPP_
#define MYSECONDCLASS_HPP_

#include <string>
#include "MyFirstClass.hpp"

namespace com {
namespace company {
namespace package2 {

    /*
     * Do not write using-declarations in header files according to
     * Herb Sutter's Namespace Rule #2.
     *
     * using std::string; // bad
     * using com::company::package1::MyFirstClass; // bad
     */

class MySecondClass{

public:
    /*
     * Public class-scoped typedefs instead of using-declarations in
     * namespace package2. Consequently we can avoid fully qualified
     * type names in the remainder of the class declaration. This
     * yields maximum readability and shows cleanly the types imported
     * from other namespaces.
     */
    typedef std::string String;
    typedef com::company::package1::MyFirstClass MyFirstClass;

    MySecondClass();
    ~MySecondClass();

    String getText() const; // no std::string required
    void setText(String as_text); // no std::string required

    void setMyFirstInstance(MyFirstClass anv_instance); // no com::company:: ...
    MyFirstClass getMyFirstInstance() const; // no com::company:: ...

private:
    String is_text; // no std::string required
    MyFirstClass inv_myFirstInstance; // no com::company:: ...
};

} // namespace package2
} // namespace company
} // namespace com

#endif /* MYSECONDCLASS_HPP_ */

MySecondClass.cpp

#include "MySecondClass.hpp"

/*
 * According to Herb Sutter's "A Good Long-Term Solution" it is fine
 * to write using declarations in a translation unit, as long as they
 * appear after all #includes.
 */
using com::company::package2::MySecondClass; // OK because in cpp file and
                                             // no more #includes following
MySecondClass::MySecondClass()
{
}

MySecondClass::~MySecondClass()
{
}

/*
 * As we have already imported all types through the class scoped typedefs
 * in our header file, we are now able to simply reuse the typedef types
 * in the translation unit as well. This pattern shortens all type names
 * down to a maximum of "ClassName::TypedefTypeName" in the translation unit -
 * e.g. below we can simply write "MySecondClass::String". At the same time the
 * class declaration in the header file now governs all type imports from other
 * namespaces which again enforces the DRY - Don't Repeat Yourself - principle.
 */

// Simply reuse typedefs from MySecondClass
MySecondClass::String MySecondClass::getText() const
{
    return this->is_text;
}

// Simply reuse typedefs from MySecondClass
void MySecondClass::setText(String as_text)
{
    this->is_text = as_text;
}

// Simply reuse typedefs from MySecondClass
void MySecondClass::setMyFirstInstance(MyFirstClass anv_instance)
{
    this->inv_myFirstInstance = anv_instance;
}

// Simply reuse typedefs from MySecondClass
MySecondClass::MyFirstClass MySecondClass::getMyFirstInstance() const
{
    return this->inv_myFirstInstance;
}

Main.cpp

#include <cstdio>
#include "MySecondClass.hpp"

using com::company::package2::MySecondClass; // OK because in cpp file and
                                             // no more #includes following
int main()
{
    // Again MySecondClass provides all types which are imported from
    // other namespaces and are part of its interface through public
    // class scoped typedefs
    MySecondClass *lpnv_mySecCls = new MySecondClass();

    // Again simply reuse typedefs from MySecondClass
    MySecondClass::String ls_text = "Hello World!";
    MySecondClass::MyFirstClass *lpnv_myFirClsf =
            new MySecondClass::MyFirstClass();

    lpnv_mySecCls->setMyFirstInstance(*lpnv_myFirClsf);

    lpnv_mySecCls->setText(ls_text);
    printf("Greetings: %s\n", lpnv_mySecCls->getText().c_str());

    lpnv_mySecCls->setText("Goodbye World!");
    printf("Greetings: %s\n", lpnv_mySecCls->getText().c_str());

    getchar();

    delete lpnv_myFirClsf;
    delete lpnv_mySecCls;

    return 0;
}
like image 262
Thomas G. Avatar asked Jan 25 '13 20:01

Thomas G.


1 Answers

Pain is mitigated by reducing complexity. You're bending C++ into Java. (That works just as bad as trying the other way.)

Some hints:

  • Remove the "com" namespace level. (This is just a java-ism that you don't need)
  • Drop the "company" namespace, maybe replace by "product" or "library" namespace (i.e. boost, Qt, OSG, etc). Just pick something that's unique w.r.t. the other libs you're using.
  • You don't need to fully declare names that are in the same namespace you're in (caveat emptor: template classe, see comment). Just avoid any using namespace directives in the headers. (And use with care in C++ files, if at all. Inside functions is preferred.)
  • Consider namespace aliases (in functions/cpp files), i.e namespace bll = boost::lambda;. This creates shortcuts that are quite neat.
  • Also, by hiding private members/types using the pimpl pattern, your header have less types to expose.

P.S: Thanks to @KillianDS a few good tips in comments (that were deleted when I edited them into the question.)

like image 168
Macke Avatar answered Sep 18 '22 11:09

Macke