6

通过我的一个项目,我将进入 C++ 领域。基本上我来自 Java 背景,想知道 Java 包的概念是如何在 C++ 世界中实现的。这让我想到了命名空间的 C++ 概念。

到目前为止,我对命名空间非常满意,但是当涉及到头文件时,对于完全限定的类名、使用指令和使用声明来说,事情变得有点低效。

Herb Sutter的这篇文章很好地描述了这个问题。

据我了解,这一切都归结为:如果您编写头文件,请始终使用完全限定的类型名称来引用其他命名空间中的类型。

这几乎是不可接受的。由于 C++ 标头通常提供类的声明,因此最大的可读性具有最高优先级。完全限定来自不同名称空间的每种类型会产生大量视觉噪音,最终会降低标头的可读性,从而引发是否使用名称空间的问题。

尽管如此,我还是想利用 C++ 命名空间,因此对这个问题提出了一些想法:如何克服 C++ 头文件的命名空间邪恶?经过一些研究,我认为 typedefs 可能是解决这个问题的有效方法。

接下来你会发现一个 C++ 示例程序,它演示了我想如何使用公共类作用域的 typedef 从其他命名空间导入类型。该程序在语法上是正确的,并且可以在 MinGW W64 上正常编译。到目前为止一切顺利,但我不确定这种方法是否会愉快地从标题中删除 using 关键字,但会带来另一个我根本不知道的问题。就像 Herb Sutter 所描述的事情一样棘手。

那就是我恳请所有对 C++ 有透彻了解的人检查下面的代码,并让我知道这是否应该工作。谢谢你的想法。

我的FirstClass.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_ */

我的FirstClass.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;
}

主文件

#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;
}
4

1 回答 1

17

通过降低复杂性来减轻痛苦。您正在将 C++ 转换为 Java。(这和尝试另一种方式一样糟糕。)

一些提示:

  • 删除“com”命名空间级别。(这只是你不需要的java-ism)
  • 删除“公司”命名空间,可能替换为“产品”或“库”命名空间(即 boost、Qt、OSG 等)。只需选择与您正在使用的其他库不同的东西。
  • 您不需要完全声明与您所在的同一命名空间中的名称(警告:模板类,请参阅注释)。只需避免using namespace标题中的任何指令。(如果有的话,在 C++ 文件中小心使用。内部函数是首选。)
  • 考虑命名空间别名(在函数/cpp 文件中),即namespace bll = boost::lambda;. 这会创建非常简洁的快捷方式。
  • 此外,通过使用pimpl模式隐藏私有成员/类型,您的标头可以公开的类型更少。

PS:感谢@KillianDS 评论中的一些很好的提示(当我将它们编辑到问题中时被删除。)

于 2013-01-25T20:53:43.837 回答