使用C++命名空间会增加耦合吗?

Mik*_*kes 7 c++ namespaces decoupling

我知道C++库应该使用命名空间来避免名称冲突,但是因为我已经不得不:

  1. #include 正确的标题(或转发声明我打算使用的类)
  2. 按名称使用这些类

这两个参数不要推断命名空间传达的相同信息.现在使用命名空间引入了第三个参数 - 完全限定名称.如果库的实现发生变化,现在我需要改变三个潜在的事情.根据定义,这不是库代码和我的代码之间耦合的增加吗?


例如,查看Xerces-C:它定义了Parser在命名空间内调用的纯虚拟接口XERCES_CPP_NAMESPACE.我可以Parser通过包含适当的头文件,然后导入命名空间using namespace XERCES_CPP_NAMESPACE或使用前缀声明/定义来使用我的代码中的接口XERCES_CPP_NAMESPACE::.

随着代码的发展,可能需要删除Xerces以支持不同的解析器.我通过纯虚拟接口部分"保护"了库实现的变化(如果我使用工厂来构建我的Parser,更是如此),但是一旦我从Xerces切换到其他东西,我需要通过我的代码梳理和改变我的一切using namespace XERCES_CPP_NAMESPACEXERCES_CPP_NAMESPACE::Parser代码.


最近,当我重构现有的C++项目以将一些现有的有用功能拆分到库中时,我遇到了这个问题:

foo.h中

class Useful;  // Forward Declaration

class Foo
{
public:

    Foo(const Useful& u);
    ...snip...

}
Run Code Online (Sandbox Code Playgroud)

Foo.cpp中

#include "foo.h"
#include "useful.h" // Useful Library

Foo::Foo(const Useful& u)
{
    ... snip ...
}
Run Code Online (Sandbox Code Playgroud)

当时很大程度上是出于无知(并且部分地出于懒惰),所有功能都useful.lib被置于全局命名空间中.

随着内容的useful.lib增长(以及更多客户端开始使用该功能),决定将所有代码移动useful.lib到其自己的名称空间中"useful".

客户端.cpp文件很容易修复,只需添加一个using namespace useful;

Foo.cpp中

#include "foo.h"
#include "useful.h" // Useful Library

using namespace useful;

Foo::Foo(const Useful& u)
{
    ... snip ...
}
Run Code Online (Sandbox Code Playgroud)

但这些.h文件真的是劳动密集型的.using namespace useful;我没有通过放入头文件来污染全局命名空间,而是将命名空间中的现有前向声明包装起来:

foo.h中

namespace useful {
    class Useful;  // Forward Declaration
}

class Foo
{
public:

    Foo(const useful::Useful& u);
    ...snip...
}
Run Code Online (Sandbox Code Playgroud)

有几十个(和几十个)文件,这最终成为一个主要的痛苦!应该不那么困难.很明显,我在设计和/或实施方面都做错了.

虽然我知道库代码应该在它自己的命名空间中,但是库代码是否有利于保留在全局命名空间中,而是尝试管理#includes

Jer*_*fin 10

听起来像你的问题主要是由于你如何(ab)使用命名空间,而不是由于命名空间本身.

  1. 听起来你把很多与微小相关的"东西"扔进了一个命名空间,大多数情况下(当你接触它时),因为它们碰巧是由同一个人开发的.至少IMO,命名空间应该反映代码的逻辑组织,而不仅仅是一堆实用程序恰好由同一个人编写的事故.

  2. 命名空间名称通常应该相当长且具有描述性,以防止出现最远的碰撞可能性.例如,我通常包括我的姓名,编写的日期以及命名空间功能的简短描述.

  3. 大多数客户端代码不需要(通常不应该)直接使用命名空间的真实名称.相反,它应该定义命名空间别名,并且在大多数代码中只应使用别名.

将第二点和第三点放在一起,我们最终得到的代码如下:

#include "jdate.h"

namespace dt = Jerry_Coffin_Julian_Date_Dec_21_1999;

int main() {

    dt::Date date;

    std::cout << "Please enter a date: " << std::flush;
    std::cin>>date;

    dt::Julian jdate(date);
    std::cout   << date << " is " 
                << jdate << " days after " 
                << dt::Julian::base_date()
                << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这消除了(或至少大大减少了)客户端代码与日期/时间类的特定实现之间的耦合.例如,如果我想重新实现相同的日期/时间类,我可以将它们放在不同的命名空间中,只需更改别名并重新编译即可在一个和另一个之间切换.

事实上,我有时使用它作为一种编译时多态机制.举个例子,我写了几个版本的小"显示"类,一个在Windows列表框中显示输出,另一个通过iostream显示输出.然后代码使用别名,如:

#ifdef WINDOWED
namespace display = Windowed_Display
#else
namespace display = Console_Display
#endif
Run Code Online (Sandbox Code Playgroud)

其余的代码只是使用display::whatever,所以只要两个命名空间都实现了整个接口,我就可以使用其中任何一个,而不需要改变其余的代码,并且没有使用指针/引用到基类的任何运行时开销具有实现的虚函数.


Dav*_*eas 9

命名空间与耦合无关.无论你是打电话useful::UsefulClass还是公正,都存在相同的耦合UsefulClass.现在,您需要完成所有重复工作的事实只会告诉您代码在多大程度上取决于您的库.

为了简化转发,您可以编写一个forward标题(在STL中有一对,您肯定可以在库中找到它),就像usefulfwd.h只转发定义库接口(或实现类或任何您需要的).但这与耦合无关.

仍然,耦合和名称空间只是无关.任何其他名称的玫瑰都会闻起来很甜,而且你的类在任何其他命名空间中都是耦合的.


dir*_*tly 6

(a)图书馆的接口/类/功能

没有比你已经拥有的更多.使用namespace-ed库组件可以帮助您防止命名空间污染.

(b)命名空间推断的实施细节?

为什么?所有你应该包括的是标题useful.h.应该隐藏实现(并且useful.cpp可能存在于动态库表单中).

您可以useful.h通过using useful::Useful声明选择性地仅包括您需要的那些类.