13

可能重复:
将#include 包装在命名空间块中是个好主意吗?

我有一个项目,log在全局命名空间 ( ::log) 中有一个类。

所以,很自然地,#include <cmath>每次我尝试实例化我的日志类的对象时,编译器都会给出一条错误消息,因为<cmath>使用许多三字母方法污染了全局命名空间,其中一个是对数函数log()

所以有三种可能的解决方案,每一种都有其独特的丑陋副作用。

  • 将日志类移动到它自己的命名空间,并始终使用它的完全限定名称访问它。我真的想避免这种情况,因为记录器应该尽可能方便使用。
  • 编写一个mathwrapper.cpp文件,该文件是项目中唯一包含 . 的文件<cmath>,并<cmath>通过namespace math. 我不想使用这种方法,因为我必须为每个必需的数学函数编写一个包装器,并且它会增加额外的调用惩罚(由-flto编译器标志部分取消)
  • 我目前正在考虑的解决方案:

代替

#include <cmath>

经过

namespace math {
#include "math.h"
}

然后通过 计算对数函数math::log()

我已经尝试过了,它确实可以按预期编译、链接和运行。但是,它确实有多个缺点:

  • 它(显然)不可能使用<cmath>,因为<cmath>代码通过它们的完全限定名称访问函数,并且不推荐在 C++ 中使用它。
  • 我有一种非常非常糟糕的感觉,就像我会被猛禽攻击并活活吃掉一样。

所以我的问题是:

  • 是否有任何建议/约定/等禁止在命名空间中放置包含指令?
  • 有什么问题吗

    • 不同的 C 标准库实现(我使用 glibc),
    • 不同的编译器(我使用 g++ 4.7,-std=c++11),
    • 链接?
  • 你有没有试过这样做?
  • 有没有其他方法可以从全局命名空间中消除数学函数?

我在 stackoverflow 上发现了几个类似的问题,但大多数都是关于包含其他 C++ 头文件,这显然是一个坏主意,而且那些没有对 C 库的链接行为做出矛盾陈述的问题。#include <math.h>另外,另外放里面有好处extern "C" {}吗?

编辑

所以我决定做可能其他人都在做的事情,并将我所有的代码放在项目命名空间中,并在包含<cmath>.

4

4 回答 4

18

不,您正在考虑的解决方案是不允许的。实际上,这意味着您正在更改头文件的含义。您正在更改其所有声明以声明不同命名的函数。

这些更改后的声明与标准库函数的实际名称不匹配,因此,在链接时,任何标准库函数都不会解析对更改后声明所声明的函数的调用,除非它们碰巧被声明extern "C"为允许的 - 但是不推荐 - 对于来自 C 标准库的名称。

ISO/IEC 14882:2011 17.6.2.2/3 [using.headers] 适用于 C 标准库头文件,因为它们是 C++ 标准库的一部分:

翻译单元应仅在任何外部声明或定义 [*] 之外包含标头,并且应在该翻译单元中对在该标头中声明的任何实体的第一次引用之前在词法上包含标头。

[*] 将包括命名空间定义。

于 2012-09-07T19:37:35.750 回答
6

为什么不将日志类放在它自己的命名空间中,并typedef namespace::log logger;以更方便的方式使用以避免名称冲突?

于 2012-09-07T19:51:45.270 回答
2

更改班级名称。没什么大不了的。;-)

不过说真的,将名称放在与任何标准标头中的名称冲突的全局名称空间中并不是一个好主意。C++03 没有明确允许<cmath>定义::log. <cmath>但是由于在现有<math.h>(可能还有一些头文件的现有静态链接库,包括数学)之上定义的实用性,实现长期不符合这一点。因此 C++11 认可了现有的做法,并允许<cmath>将所有内容转储到全局命名空间中。C++11 还保留所有用于外部“C”链接的名称,以及所有用于 C++ 链接的函数签名,即使您不包含标头。但稍后会详细介绍。

因为在 C++ 中,任何标准头文件都可以定义来自任何其他标准头文件的名称(即,它们可以相互包含),这意味着任何标准头文件都可以定义::log. 所以不要使用它。

关于不同实现的问题的答案是,即使您的方案开始工作(不能保证),在其他一些实现中也可能有您使用的标头(或希望将来在同一个 TU 中使用)您的日志类),其中包括<cmath>,并且您没有对其进行namespace math处理。在我<random>看来,在我看来是一个候选人。它提供了一大堆连续的随机数分布,这些分布似乎可以通过数学函数实现。

我建议Log,但我喜欢大写的类名。部分原因是它们总是与标准类型和函数不同。

另一种可能性是像以前一样定义您的类并使用struct log. log这不会与函数冲突,原因只有在您花费太多时间在 C 和 C++ 标准上时才会变得清楚(您仅用log名,而不是函数,也不是带有“C”的名称)链接,所以你不会侵犯保留名称。尽管看起来相反,C++ 中的类名仍然与其他名称处于平行宇宙中,就像structC 中的标签一样)。

不幸struct log的是,它不是简单类型标识符,因此例如您不能使用struct log(VERY_VERBOSE, TO_FILE). 要定义简单类型标识符:

typedef struct log Log;
Log(VERY_VERBOSE, TO_FILE); // unused temporary object

我在下面的评论中所说的一个例子,基于一个陈述的示例用法。我认为这是有效的,但我不确定:

#include <iostream>
#include <cmath>
using std::log; // to enforce roughly what the compiler does anyway

enum Foo {
    foo, bar
};

std::ostream &log(Foo f) { return std::cout; }

int main() {
    log(foo) << log(10) << "\n";
}
于 2012-09-07T21:08:16.727 回答
2

它也很丑陋,但我相信不会导致任何链接器问题。只需重新定义日志名称<math.h>

 #define log math_log
 #include <math.h>
 #undef log

使用此日志可能会导致数学内联函数出现问题,但也许你会很幸运......

数学 log() 仍然可以访问,但这并不容易。在要使用它的函数中,只需重复其真实声明:

    int somefunc() {
        double log(double); // not sure if correct
        return log(1.1);
    }
于 2012-09-07T21:08:58.067 回答