C++ 仅头文件库避免“使用命名空间”污染

Posted

技术标签:

【中文标题】C++ 仅头文件库避免“使用命名空间”污染【英文标题】:C++ header-only library avoid "using namespace" pollution 【发布时间】:2015-05-23 21:19:51 【问题描述】:

我有一个带有多个命名空间的仅标头 C++ 库。

例如一个头文件可能包含

//header1.h
namespace library
namespace componentA
   template<typename T>
   class Someclass;


还有一个

//header2.h
namespace library
namespace componentB
   template<typename T>
   class SomeOtherClass
       void Foo(const componentA::Someclass<T>& reference);
       void Bar(const componentA::Someclass<T>& reference);
   ;


现在虽然这可行,但拥有一个仅包含头文件的库,一次又一次地编写命名空间变得乏味,尤其是当您涉及多个类和嵌套命名空间时。

所以我这样做了:

//new header2.h
namespace library
namespace componentB

   using namespace componentA;

   template<typename T>
   class SomeOtherClass
       void Foo(const Someclass<T>& reference);
       void Bar(const Someclass<T>& reference);
       void FooBar(const Someclass<T>& reference);
       void FooWithBar(const Someclass<T>& reference);
   ;


虽然这当然更方便键入,但它的问题是,现在库的客户端也可以通过像这样使用 componentB 命名空间来使用 Someclass&lt;T&gt;,这会导致接口模棱两可并最终导致代码不一致. 例如,客户现在可以使用 componentB::Someclass&lt;T&gt; 尽管它最初是在 componentA 中定义的

有没有办法让速记只能“私下”使用?

【问题讨论】:

【参考方案1】:
namespace library 
  namespace componentAImpl
    using ::library::componentB;
    // ...
    inline namespace exports
      struct foo;
    
  
  namespace componentA
    using namespace library::componentAImpl::exports;
  

用户可以访问componentAImpl,但不应该。

与此同时,library::componentA 中公开了一组干净的符号。

【讨论】:

不错。我认为这是正确的解决方案 /cc @mightyuhu @andy 我可能会使用不同的 library 命名空间,并以 details_ 开头的名称以避免诱惑代码完成者。而且我不确定 ADL 在某些情况下将如何工作。所以你必须部署它,看看它是否有粗糙的地方(我之前没有在我的代码中使用过上述模式)。【参考方案2】:

如果我正确理解您的问题,那么答案是否定的。如果在来自 componentA 命名空间的类型名称之前输入/读取 componentA:: 是个大问题(老实说,这是我的偏好),那么您可以使用短命名空间别名:

namespace library  namespace componentB

// ...
namespace ca = componentA;
// ...
void foo(ca::SomeClass<T>);
// ...
 

无论如何,我不鼓励在头文件中使用指令:它们会导致名称冲突和维护问题。

【讨论】:

是的,这样可以避免输入全名,但是客户端仍然可以访问 library::componentB::ca::Someclass 然后... @mightyuhu:是的。恐怕这是不可避免的。 AFAIK C++ 的基于翻译单元和包含文件的系统没有提供更好的选择。 如果你有兴趣看看我的回答,也许我已经监督了一些事情【参考方案3】:

嗯,C++ 11? Using-declaration 将另一个命名空间的成员引入当前命名空间或块作用域。

namespace library

    namespace componentB

       using componentA::SomeOtherClass;

       template<typename T>
       SomeOtherClass
           void Foo(const Someclass<T>& reference);
           void Bar(const Someclass<T>& reference);
           void FooBar(const Someclass<T>& reference);
           void FooWithBar(const Someclass<T>& reference);
       ;
    


【讨论】:

这不会改变客户端可以写 componentB::SomeOtherClass 的事实,即使 SomeOtherClass 位于命名空间 componentA 中。 Andy Prowl,当然,但那将是另一个范围? 我不确定我明白你的意思 我会做一个额外的块范围来防止这种情况。 等等...额外的范围可能不是头库的解决方案。但无论如何对我来说使用声明更干净,但一些恶意用户当然可能会试图违反理智。【参考方案4】:

经过一段时间的思考,我自己想出了解决方案。不幸的是,这并不简单。

//header1.h
namespace lib_noexport

namespace library
namespace componentA
   template<typename T>
   class Someclass;





using namespace lib_noexport;

然后

//header2.h
namespace lib_noexport

using library::componentA::Someclass;

namespace library

    namespace componentB

       template<typename T>
       class SomeOtherClass
           void Foo(const Someclass<T>& reference)
       ;
    



using namespace lib_noexport;

现在这会产生以下结果:

library::componentB::SomeOtherClass<int> a; //works as expected
Someclass<int> b; //error
library::componentB::Someclass<int> c; //error
library::componentA::Someclass<int> b; //works

尽管如此,用户可能会愚蠢到使用未记录的 lib_noexport:: 但是我不能再帮助她了..

【讨论】:

您需要inner 命名空间吗?

以上是关于C++ 仅头文件库避免“使用命名空间”污染的主要内容,如果未能解决你的问题,请参考以下文章

C++命名空间与缺省参数

C++命名空间与缺省参数

C++命名空间

C++命名空间

C++入门之命名空间

C++基础学习笔记命名空间 namespace 的理解和使用