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

时间:2010-02-04 19:36:21

标签: c++ namespaces decoupling

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

  1. #include正确的标题(或者转发声明我打算使用的类)
  2. 按名称使用这些类
  3. 这两个参数不要推断命名空间传达的相同信息。现在使用命名空间引入了第三个参数 - 完全限定名称。如果库的实现发生了变化,那么现在我需要改变三个潜在的东西。根据定义,这不是库代码和我的代码之间耦合的增加吗?


    例如,查看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...
    
    }
    

    Foo.cpp中

    #include "foo.h"
    #include "useful.h" // Useful Library
    
    Foo::Foo(const Useful& u)
    {
        ... snip ...
    }
    

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

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

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

    Foo.cpp中

    #include "foo.h"
    #include "useful.h" // Useful Library
    
    using namespace useful;
    
    Foo::Foo(const Useful& u)
    {
        ... snip ...
    }
    

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

    foo.h中

    namespace useful {
        class Useful;  // Forward Declaration
    }
    
    class Foo
    {
    public:
    
        Foo(const useful::Useful& u);
        ...snip...
    }
    

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

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

7 个答案:

答案 0 :(得分:10)

听起来我的问题主要是因为您(ab)如何使用命名空间,而不是由于命名空间本身。

  1. 听起来你把很多与微小相关的“东西”投入到一个命名空间中,大多数情况下(当你开始讨论它时),因为它们碰巧是由同一个人开发的。至少IMO,命名空间应该反映代码的逻辑组织,而不仅仅是一堆实用程序碰巧由同一​​个人写的事故。

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

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

  4. 将第二点和第三点放在一起,我们最终会得到类似这样的代码:

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

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

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

    #ifdef WINDOWED
    namespace display = Windowed_Display
    #else
    namespace display = Console_Display
    #endif
    

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

答案 1 :(得分:9)

命名空间与耦合无关。无论您将其称为useful::UsefulClass还是仅UsefulClass,都存在相同的耦合。现在,您需要完成所有重复工作的事实只会告诉您代码在多大程度上取决于您的库。

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

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

答案 2 :(得分:6)

  

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

不超过你已经拥有的。使用namespace - ed库组件可以帮助您防止命名空间污染。

  

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

为什么呢?您应该包括的是标题useful.h。应该隐藏实现(并且驻留在useful.cpp中,并且可能以动态库形式存在)。

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

答案 3 :(得分:2)

我想扩展DavidRodríguez的第二段 - dribeas的回答(upvoted):

  

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

我认为这指出了问题的核心。命名空间在这里是一个红色的鲱鱼,你被低估了包含语法依赖的需要而被咬了。

我可以理解你的“懒惰”:它 不适合过度工程(企业 HelloWorld.java),但是如果你在开始时保持你的代码低调(这不一定是错的)并且代码证明是成功的,成功将它拖到联盟之上。诀窍是感觉正确的时刻切换到(或从需要出现的第一时刻开始使用)一种以向前兼容的方式划伤你的痒的技术

对一个项目的激动人心的前瞻声明只是要求第二轮和后续轮次。你真的不需要成为一名C ++程序员来阅读建议“不要转发声明标准流,而是使用<iosfwd>”(尽管这已经过了几年,这是相关的; 1999年?VC6时代,绝对)。你可以听到很多程序员痛苦的尖叫声,如果你暂停一下,他们就不会听从你的意见。

EM>。只需这个简单的授权,您就可以免除#include <usefulfwd.h>class Useful的{​​{1}}次更改。

当然,它无法帮助您完成客户端代码中的所有用途。简单的帮助:实际上,如果在大型应用程序中使用库,则应将库提供的前向头文件包装在特定于应用程序的头文件中。这种情况的重要性随着依赖的范围和图书馆的波动性而增长。

的src / libuseful / usefulfwd.h

N-1

的src / MyApp的/ MyApp的-usefulfwd.h

class Useful

基本上,这是保持代码 DRY 的问题。您可能不喜欢引人注目的TLA,但这个描述了一个真正的核心编程原则。

答案 4 :(得分:0)

如果您有多个“有用”库的实现,那么它们是否同样可能(如果不在您的控制之下)它们将使用相同的命名空间,无论它是全局命名空间还是有用的名称空间?

换句话说,使用命名的命名空间与全局命名空间的关系与你对库/实现的“耦合”无关。

任何连贯的库演化策略都应该为API保持相同的命名空间。实现可以使用对您隐藏的不同命名空间,这些可能会在不同的实现中发生变化。不确定这是否是“命名空间推断的实现细节”的意思。

答案 5 :(得分:0)

不,你没有增加耦合。正如其他人所说 - 我没看到名称空间使用如何泄露实现

消费者可以选择

 using useful;
 using useful::Foo;
 useful::Foo = new useful::Foo();

我的投票总是为最后一次 - 它是污染最少的

第一个人应该强烈劝阻(通过行刑队)

答案 6 :(得分:0)

嗯,事实是,没有办法轻易避免C ++中的代码纠缠。但是,使用全局命名空间是最糟糕的想法,因为那时你无法在实现之间进行选择。最终使用Object而不是Object。这在内部工作正常,因为您可以随时编辑源代码,但如果有人将这样的代码发送给客户,他们就不应该期待它们长久。

一旦你使用using语句,你也可能在全局,但在cpp文件中使用它可能会很好。所以我会说你应该拥有命名空间中的所有东西,但对于内部它应该都是相同的命名空间。这样,其他人可以在没有灾难的情况下使用您的代码。