【问题标题】:Why is namespace composition so rarely used?为什么命名空间组合很少使用?
【发布时间】:2013-06-01 09:50:29
【问题描述】:

在他的书C++ 编程语言(第三版)中,Stroustrup 教导如何在自己的命名空间中定义单个组件并将它们导入到通用命名空间中。

例如:

namespace array_api {    
    struct array {};    
    void print(const array&) { }
}

namespace list_api {
    struct list {};        
    void print(const list&) { }
}

namespace api {
    using array_api::array;
    using list_api::list;
}

我看起来很有趣,但我从未见过实际使用过这种方法。

为什么这种技术几乎从不使用?

【问题讨论】:

  • 我会说因为它需要更多的努力而不是不做:P 人们是懒狗
  • 在大多数情况下,因为人们不知道它的用途,以及它会给项目带来什么好处。
  • 反例:提升。大多数 boost 库都有自己的命名空间,然后提供 方便的标头,不仅包含许多标头,而且还可以在命名空间层次结构中将其符号导入上一级,直到主级别 @987654322 @标头。
  • 正如 B. Stroustroup 自己指出的那样,它的用处在大型项目中最为明显(例如 Boost)——“命名空间是一个范围。因此,‘命名空间’是一个非常基本且相对的简单的概念。程序越大,命名空间就越有用,可以表达其部分的逻辑分离。普通的局部作用域、全局作用域和类都是命名空间。"
  • 对你有好处。我确实看到使用像这样或像 java 这样的命名空间,这是一个可怕的混乱。在我有权力的地方,我允许每个说 250 KLOC 的名称空间。如果你发布一个库,当然,把它放在它自己的单一命名空间中,但在应用程序中它通常只会造成麻烦。

标签: c++


【解决方案1】:

我主要想知道有什么好处(正如 Raymond Chen 所说,每个功能都以 -100 分开始)。但是,我要提供一个对位:Luabind,它确实使用了类似这样的东西。请参阅luabind/object.hpp,它本质上说:

namespace luabind {
  namespace adl {
    class object {
    };
  }
  using adl::object;
}

仅从名称我们就可以推断出动机:支持依赖于参数的查找。给定用户所知道的luabind::object,实际上是luabind::adl::object,编译器将自动从luabind::adl 命名空间中发现相关函数。然而,用户可能不需要以非常明确的方式了解这些功能,不会“污染”主要的 luabind 命名空间。所以这很好,我猜。

但是你知道什么不好吗?转发声明这些类之一。这失败了:

namespace luabind { class object; }

您需要这样做:

namespace luabind { namespace adl { class object; } }

因此,抽象很快就会泄露,因为毕竟用户确实需要了解这个神奇的 adl 命名空间。

【讨论】:

  • 这可能表明使用该技术的库编写者应该提供例如lib/submodule/foo.hpplib/submodule/foo_fwd.hpp
  • -1 没有解释这与 ADL 的关系。您不需要做任何特别的事情来支持 ADL,它“可以正常工作”。对于有缺陷的编译器或特定的极端情况,这看起来像是一种解决方法。
  • @Potatoswatter:我确实说过“但是那些用户可能不需要以非常明确的方式了解的功能,并没有“污染”主 luabind 命名空间。因此,虽然我们知道 ADL 允许我们将函数放在 adl 命名空间中,但我确实指出了一个可能的(如果很脆弱)原因,我们可能不想将所有内容都转储到更高级别的命名空间中。我仍然不认为总体上采用这种技术是/足够/理由;也许 Daniel Wallin 有一天会找到这个并告诉我们他为什么这样做。
  • @JohnZwinck 如果“公共”API 函数在 luabind:: 中,但类在 luabind::adl:: 中定义,则讽刺的是,ADL 不适用于调用 API 的用户。如果 ADL 对内部实现非常重要,还有其他方法可以将公共类与私有命名空间相关联,例如标记调度。但通常情况正好相反:对用户 API 比对内部(所有函数都在范围内)更重要。
【解决方案2】:

仅仅是因为没有人学习它。大多数程序员都学习过 Java 风格的 OOP,更常见的是 C++ 代码将命名空间风格的 API 封装到 class 中。

C++ 具有依赖于参数的函数查找 (ADL),它允许它根据调用它的参数类型的名称空间从 API 中选择一个函数。这是一个强大的机制,可以让您绕过大部分 OOP 样板,但仍保留其优势。但它并没有真正教给初学者,没有什么特别的理由。

对于它的价值,你的例子大致相当于这个浓缩版:

namespace api {

struct array {
    friend void print(const array&) { }
};

struct list {       
    friend void print(const list&) { }
};

}

当您在类中将函数定义为friend 时,它不属于该类,而是属于封闭的命名空间。此外,它的名称仅在类中可用,而不是封闭的命名空间,除非使用类作为参数调用它。

array_apilist_api 命名空间污染全局命名空间是个坏主意。最好像我的示例那样创建层次结构。

因此,鉴于上述情况,您可以这样做:

api::array x;

print( x ); // ADL finds api::print defined inside api::array

确实,这就是 iostream 样式 operator << 的工作原理。您不需要 using 声明(或指令)来打印库中的对象。

【讨论】:

    【解决方案3】:

    我猜是因为它减少了封装。在您的示例中,当您编写 map_api 以转到 api.h 并将其导入 api 命名空间时,这将是一个正确的痛苦。拥有一个导入每个子命名空间的大标题 api.h 并不完全包含最小化。

    • 地图.h:

      namespace map_api { /* fns */ }
      
    • api.h:

      #include <map.h>
      namespace api { using map_api::map; }
      

    或者,考虑其他标题布局:如果导入 api 命名空间发生在另一个文件中:

    • map_internal.h:

      namespace map_api { /* fns */ }
      
    • map_api.h:

      #include <map_internal.h>
      namespace api { using map_api::map; }
      

    这也很痛苦,不得不像这样将 api 拆分成两个文件。

    最后一种可能性是在一个文件中完成所有操作:

    • 地图.h:

      namespace map_api { /* fns */ }
      namespace api { using map_api::map; }
      

    在这种情况下,如果它们都平等地暴露给所有客户端/包含器,那么我想知道将事物放在相同抽象级别的两个命名空间中的意义何在..?

    【讨论】:

      【解决方案4】:

      我觉得其他答案遗漏了组成命名空间的第一种、最简单和最有用的方法:只使用你需要的东西

      namespace Needed {                                                                                                                                                                            
        using std::string;                                                                                                                                                                          
        using std::bind;                                                                                                                                                                            
        using std::function;                                                                                                                                                                        
        using std::cout;                                                                                                                                                                            
        using std::endl;                                                                                                                                                                            
        using namespace std::placeholders;                                                                                                                                                          
      }                                                                                                                                                                                             
      
      
      int main(int argc, char* argv[])                                                                                                                                                              
      {                                                                                                                                                                                             
      
        /*  using namespace std;                                                                                                                                                                    
            would avoid all these individual using clauses,                                                                                                                     
            but this way only these are included in the global                                                                                                                                      
            namespace.                                                                                                                                                                          
        */                                                                                                                                                                                          
      
       using namespace Needed;  // pulls in the composition
      
       string s("Now I have the namespace(s) I need,");
      
       string t("But not the ones I don't.");
      
       cout << s << "\n" << t << endl;                                                                                               
      
       // ...
      

      因此避免了与 STL 的其他部分的任何冲突,以及 需要在常用功能前加上std::

      【讨论】:

        猜你喜欢
        • 2014-09-22
        • 2020-11-24
        • 1970-01-01
        • 2012-12-04
        • 2011-03-20
        • 2017-09-20
        • 1970-01-01
        • 1970-01-01
        • 2011-12-10
        相关资源
        最近更新 更多