#includes在命名空间内,在命名空间中"嵌入"预先写好的东西

Kra*_*lew 3 c++ templates namespaces

简介:这样做是否安全

 namespace Foo {
 #include "bar"
 }
Run Code Online (Sandbox Code Playgroud)

在你轻率地拒绝之前,我想我有一些规则允许它相当安全.

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

总的来说,externs和forward声明在命名空间内不能很好地工作.

所以我猜我正在问

a)还有什么其他的问题

b)有更好的方法

== A [[Header-only library]] ==

我喜欢写图书馆.[[仅限标题库和链接库]].

例如

#include "Valid.hpp"
Run Code Online (Sandbox Code Playgroud)

为简单的包装器类型定义模板Valid.

(不要陷入困境"你应该使用一些标准库而不是你自己的.这是一个例子.如果Boost或C++已经标准化,我不知道.我一直在使用包装器,因为模板被添加到C++中. )

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

std :: string to_string(const Valid&v){std :: ostringstream oss; if(v.valid()){oss << v; } else {"invalid"; } return 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 ) { ...
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.

我可以直截了当地使用Valid.

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

但有时会发生碰撞.有时别人有自己的有效.

命名空间救援,对吧?但我不想更改所有现有代码以使用命名空间.所以,我在一个有碰撞的新项目中受到诱惑

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

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

    ...
Run Code Online (Sandbox Code Playgroud)

问题:包含的标题不再是独立的.内部定义的所有内容都没有放在名称空间AG :: Wrapper中.

"修复"并不难.
我们"必须"做的是包括Valid.hpp所依赖的所有顶级库.如果他们有警卫,他们将不会被重新包括在内.

   #include <iostream>
    #include <sstream>

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

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

    ...
Run Code Online (Sandbox Code Playgroud)

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

更糟糕的是,有时只有头文件库包含外部声明和外部声明.这些声明也放在命名空间内.特别是,如果extern声明在命名空间中定义的函数内.

即有时我们使用extern和forward声明,而不是包含整个头文件.这些包含在命名空间中.

问:有更好的方法吗?

== ::不这样做==

提示:::不这样做.至少不是所有的时间,不是在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?
 };
Run Code Online (Sandbox Code Playgroud)

但这不仅仅是含糊不清.

int var;

 namespace Foo {
 void bar() {
    extern int var;
 };
Run Code Online (Sandbox Code Playgroud)

作品 - Foo :: bar'svar等于:: var.

但它只能起作用,因为命名空间之外的声明.

以下不起作用

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

虽然如下:

header int var; cpp void bar(){extern int var; }}

基本上,这相当于说将函数置于命名空间内并不是一个微不足道的重构.围绕一大块代码包装命名空间,无论它是否包含#include,都是不够的.......至少不存在外部或前方声明.

即使你

==关于在名称空间内放置包含的意见==

Stackoverflow人员似乎反对在名称空间中放置#includes:

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

...你永远不应该在命名空间中写一个#include."永远"的意思是"除非你做的事情真是模糊不清,我没有想到并证明这一点".您希望能够查看文件,并查看其中所有内容的完全限定名称.如果有人稍后出现并且通过在其名称空间内包含额外的命名空间而无法做到这一点. - Steve Jessop 12年1月6日16:38

总体问题:

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

即我想能够说

namespace A {
void foo() {
   // --- here is a reference to gloal scope extreren ...
Run Code Online (Sandbox Code Playgroud)

Seb*_*edl 6

我知道这是一个老问题,但无论如何我想提供更详细的答案.另外,给出潜在问题的真实答案.

如果您在命名空间中包含标头,那么这里可能会出现一些问题.

  1. 标头包括其他标头,然后它们也包含在命名空间内.然后,另一个地方也希望包含这些标头,但是从命名空间外部.因为标题包含防护,所以只有一个包含实际生效,并且标题中定义的东西的实际命名空间突然巧妙地取决于您包含其他标题的顺序.

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

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

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

所以从理论上讲,你可以设计在命名空间中包含时可以工作的头文件,但是它们很烦人(必须将所有依赖项冒泡到包装器中)并且非常受限制(不能使用完全限定名称或专门用于另一个library的命名空间,必须是header-only).所以不要这样做.

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

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

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

#ifndef YOURLIB_LEGACY_THE_HEADER_H
#define YOURLIB_LEGACY_THE_HEADER_H

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

#endif
Run Code Online (Sandbox Code Playgroud)

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

对于新代码,技巧不是包含"namespaced/the_header.h",而是更改项目设置,以便include目录指向命名空间子目录而不是库根.然后你可以简单地包含"the_header.h"并获得命名空间版本.