4

简介:这样做是否安全

 namespace Foo {
 #include "bar"
 }

在你轻率地说不之前,我想我有一些规则可以相当安全地允许它。

但我不喜欢它们,因为它们要求包含器单独包含所需的所有全局范围标头。尽管这可能是可以容忍的,但如果我们想象在命名空间中包含只是一个特殊的管理功能。

总的来说,外部和前向声明在命名空间内不能很好地工作。

所以我想我在问

a) 还有什么其他问题

b) 有没有更好的方法

== A [[仅标题库]] ==

我喜欢写图书馆。[[仅头文件库和链接器库]]。

例如

#include "Valid.hpp"

为简单的包装器类型定义了一个有效的模板。

(不要陷入“你应该为此使用一些标准库而不是你自己的库。这是一个例子。我不知道 Boost 或 C++ 是否已经标准化了这个。自从模板被添加到 C++ 以来,我一直在使用包装器。 )

另外,让我们说,它是一个只有头文件的库,它在 Valid.hpp 中定义了一个打印函数

std::string to_string( const Valid& v ) { std::ostringstream oss; if( v.valid() ) { oss << v; } 其他 {“无效”;} 返回 oss.str(); }

而且因为我认为这是正确的做法,所以我让 Valid.hpp 包含它所依赖的标头:

   Valid.hpp:

         #include <iostream>
         #include <sstream>

         template<typename T> 
         class Valid {
         private:
           T value_;
           bool valid_
           ...
         };

         ...

         std::string to_string( const Valid<T>& v ) { ...

到目前为止,一切都很好。

我可以直接使用Valid。

== 名称冲突 - 尝试在命名空间中使用 include 来解决 ==

但有时也会发生碰撞。有时其他人有自己的Valid。

命名空间来拯救,对吧?但我不想更改所有现有代码以使用命名空间。所以,我很想,在一个有冲突的新项目中,做

   namespace AG {
    namespace Wrapper {
    #include "lib/AG/Wrapper/Valid.hpp"
    }
    }

    AG::Wrapper::Valid<T> foo_v; 

    ...

问题:包含的标题不再是独立的。里面定义的所有东西都没有放在命名空间 AG::Wrapper 中。

“修复”并不难。
我们“必须”做的就是包含 Valid.hpp 所依赖的所有顶级库。如果他们有包含警卫,他们将不会被重新包含。

   #include <iostream>
    #include <sstream>

    namespace AG {
    namespace Wrapper {
    #include "lib/AG/Wrapper/Valid.hpp"
    }
    }

    AG::Wrapper::Valid<T> foo_v; 

    ...

但它不再是独立的。:-(

更糟糕的是,有时只有标头的库包含外部声明和前向声明,它们在其外部。这些声明也被放置在命名空间内。特别是,如果 extern 声明位于命名空间中定义的函数内部。

即有时我们使用外部和前向声明,而不是包含整个头文件。这些被包含在命名空间中。

问:有没有更好的方法?

== :: 不这样做 ==

提示::: 不这样做。至少不是一直,不是在 gcc 4.7.2 中。
(Gcc 在这方面的行为随着时间的推移而改变。Gcc 4.1.2 的行为有所不同。)

例如

 Type var;

 namespace Foo {
 void bar() {
    extern ::Type      ::var;;
    extern ::Type      ::Foo::bar;
    extern ::Type::foo      ::bar;  // see the ambiguity?
 };

但这不仅仅是模棱两可。

整数变量;

 namespace Foo {
 void bar() {
    extern int var;
 };

有效 - Foo::bar'svar 等于 ::var。

但它只是因为命名空间之外的声明才有效。

以下不起作用

标头 int var; cpp namespace Foo { void bar() { extern int var; } }

虽然以下是:

标头 int var; cpp void bar() { extern int var; } }

基本上,这等于说,将函数放在命名空间中并不是一项微不足道的重构。将命名空间包裹在一块代码周围,无论它是否是#include'd,都是不够的。...至少在有外部或前向声明的情况下不会。

而且即使你

== 反对将包含放入命名空间的意见 ==

Stackoverflow 的人似乎反对将#includes 放在命名空间中:

例如,如何使用命名空间内单独标头中定义的类

...您永远不应该在命名空间内编写#include。“从不”的意思是,“除非你正在做一些我没有想到的非常晦涩的事情并且证明它是合理的”。您希望能够查看文件,并查看其中所有内容的完全限定名称。如果有人稍后出现并通过在其命名空间中包含一个额外的命名空间,您将无法这样做。——史蒂夫·杰索普 2012 年 1 月 6 日 16:38

总体问题:

有没有办法从命名空间的深处说“现在这里有一些我依赖于外部世界的名称,而不是在命名空间内。”?

即我想说

namespace A {
void foo() {
   // --- here is a reference to gloal scope extreren ...
4

3 回答 3

8

我知道这是一个老问题,但无论如何我想给出更详细的答案。另外,对根本问题给出真正的答案。

如果您在命名空间中包含标头,这只是一些可能出错的事情。

  1. 标头包括其他标头,然后这些标头也包含在命名空间中。然后另一个地方也想包含这些标头,但来自命名空间之外。因为标头具有包含保护,实际上只有一个包含生效,并且标头中定义的内容的实际名称空间突然微妙地取决于您包含其他标头的顺序。

  2. 标头或其任何包含的标头应位于全局命名空间中。例如,标准库头文件经常(为了避免冲突)将其他标准内容(或实现细节)称为::std::other_stuff,即期望std直接位于全局命名空间中。如果您在命名空间中包含标头,则不再是这种情况。这些东西的名称查找将失败,并且标头将不再编译。它不仅仅是标准标题;我敢肯定在 Boost 标头中也有这种情况。

  3. 如果您通过确保首先包含所有其他标头来解决第一个问题,并通过确保不使用完全限定名称来解决第二个问题,那么事情仍然可能出错。一些图书馆要求其他图书馆专门研究他们的东西。例如,一个库可能想要专门化std::swapstd::hash或者std::less针对它自己的类型。(您可以改为重载std::swap,但不能为std::hashandstd::less这样做。)这样做的方法是关闭特定于库的命名空间,打开命名空间std,然后将专业化放在那里。除非库的标头包含在任意深度嵌套的命名空间中,否则它不能关闭这些命名空间。它尝试打开的命名空间std不会是::std,但是::YourStuff::std,它可能不包含任何要专门化的主要模板,即使它包含,这仍然是错误的做法。

  4. 最后,命名空间中的事物与外部事物的名称不同。如果您的库不是仅标头但具有编译部分,则编译部分可能没有将所有内容嵌套在名称空间中,因此库中的内容与您刚刚包含的内容具有不同的名称。换句话说,您的程序将无法链接。

因此,理论上,您可以设计包含在命名空间中时可以工作的标头,但它们使用起来很烦人(必须将所有依赖项都冒泡到包含器)并且非常受限(不能使用完全限定名称或在另一个中专门化东西库的命名空间,必须是仅标头)。所以不要这样做。

但是您有一个不使用名称空间的旧库,并且您想更新它以使用它们而不破坏所有旧代码。这是你应该做的:

首先,将子目录添加到库的包含目录。称它为“命名空间”或类似的东西。接下来,将所有标头移动到该目录并将其内容包装在名称空间中。

然后将转发标头添加到基本目录。对于库中的每个文件,您添加一个如下所示的转发器:

#ifndef YOURLIB_LEGACY_THE_HEADER_H
#define YOURLIB_LEGACY_THE_HEADER_H

#include "namespaced/the_header.h"
using namespace yourlib;

#endif

现在旧代码应该像往常一样工作。

对于新代码,诀窍不是包含“namespaced/the_header.h”,而是更改项目设置,使包含目录指向命名空间子目录而不是库根目录。然后您可以简单地包含“the_header.h”并获取命名空间版本。

于 2013-07-23T09:56:35.167 回答
4

我不认为这是安全的。您将所有包含放入命名空间 Foo...想象一下您的一些包含包含来自 std 命名空间的某些内容...我无法想象混乱!

我不会那样做的。

于 2013-01-11T01:59:41.537 回答
-1

头文件不是黑盒子。您始终可以查看包含在项目中的标头,并查看将其包含在命名空间块中是否安全。或者更好的是,您可以修改标头本身以添加命名空间块。即使标头来自第三方库并且在后续版本中发生更改,您在项目中拥有的标头也不会更改。

于 2013-01-11T02:01:29.220 回答