【问题标题】:Is it OK to put a standard, pure C header #include directive inside a namespace? [duplicate]可以将标准的纯 C 标头 #include 指令放在命名空间中吗? [复制]
【发布时间】:2012-09-01 16:52:28
【问题描述】:

可能重复:
Is it a good idea to wrap an #include in a namespace block?

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

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

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

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

替换

#include <cmath>

通过

namespace math {
#include "math.h"
}

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

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

  • (显然)不可能使用 &lt;cmath&gt;,因为 &lt;cmath&gt; 代码通过其完全限定名称访问函数,并且不推荐在 C++ 中使用。
  • 我有一种非常非常糟糕的感觉,就像我是gonna get attacked and eaten alive by raptors.

所以我的问题是:

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

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

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

编辑

所以我决定做可能其他人都在做的事情,并将我的所有代码放在项目命名空间中,并在包含 &lt;cmath&gt; 时使用它的完全限定名称访问记录器。

【问题讨论】:

  • 错了。有很多原因 :) 你可以做到(它会编译,甚至看起来像你认为你正在尝试做的事情)......但你应该 '不正确的事情是把你的命名空间声明inside每个头文件。恕我直言...
  • @paulsm4 为什么?您引用了“很多原因”,但没有提供任何原因。因为我们显然不会去编辑我们编译的每个系统上的标准头文件,也不会分发我们自己的,所以似乎非常合乎逻辑地使用名称间距功能将 c 函数从全局命名空间中取出。

标签: c++ namespaces include


【解决方案1】:

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

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

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

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

    int somefunc() {
        double log(double); // not sure if correct
        return log(1.1);
    }

【讨论】:

  • 不幸的是,这会导致链接器错误:pastebin.com/2HMcma7w,因为它试图链接未知方法 math_log。
  • 我相信你不需要从数学中使用这个 log() 函数。至少来自您要使用日志的文件
  • 你是对的。这确实是一个丑陋的黑客,因为它使数学 log() 完全无法访问,并且在一些非常不切实际的情况下,expf() 可能在内部依赖 log(),但这是解决我的问题的第四种可能方法,我明确要求其他方法,所以 +1
【解决方案2】:

Change your class's name. Not that big of a deal. ;-)

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

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

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

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

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

很遗憾,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";
}

【讨论】:

  • 好吧,不幸的是,我喜欢我的类名小写,因为标准库名称就是这样写的:P。我的类的用法实际上是这样的:log(lvl::ERROR) &lt;&lt; "the logarithm of 10 is " &lt;&lt; log(10);
  • @mic_e:不幸的是,喜欢编写具有已定义行为的代码;-p。如果不是Log,那logg呢?但是,在您的示例用法中,您实际上并不需要 log 成为一个类,它可能是一个 C++ 链接函数,其参数类型是某个枚举,其中 lvl::ERROR 是一个值。您不能重载命名空间std 中的函数,但AFAIK 可以重载来自命名空间std 的全局命名空间中的函数。我仍然不建议它,但我认为它会飞。大概吧。
  • 哦,通过引用返回一个已经存在的流,或者通过值返回一个重命名日志类的实例。这需要将operator&lt;&lt; 作为成员函数或采用右值引用的非成员函数。
  • 如果您对我的课程的实际工作感兴趣,请查看这个简化版本:pastebin.com/Ndg20FJ8 不幸的是,我已经尝试使用函数来执行此操作,但无济于事,因为析构函数调用在功能中起着核心作用,因此我必须避免额外的返回值析构函数调用。此外,“定义的行为”听起来真的很棒:)
  • @mic_e:构造函数调用和按值返回对象的函数调用之间并没有太大的区别。它们都创建了一个在完整表达式末尾被销毁的临时对象,不同之处在于构造函数不能使用名称log(因为优先选择函数),而函数调用可以。根据 return 语句中的对象是否被复制省略,您可能会得到不同的行为,但我认为如果您愿意特殊情况下没有写入任何内容的日志,您可以解决这个问题,什么都不做。
【解决方案3】:

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

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

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

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

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

【讨论】:

  • 接受 ISO/IEC 标准报价。仍在决定我是否会选择解决方案 #0 或解决方案 #1。
  • "在链接时,没有一个标准库函数将解析对更改后的声明所声明的函数的调用。"那么为什么我没有收到任何链接器错误?
  • @mic_e:您实际使用了&lt;math.h&gt; 的哪些功能?有些可能以宏的形式提供。
  • 我用的是expf,在这个贴里你可以看到:pastebin.com/0vnSud4L 方法链接了,链接成功了。
  • @mic_e:您使用的函数很可能在math.h 中声明为extern "C"。尽管在 C 和 C++ 组合实现中可能非常很常见,但从技术上讲,您不能依赖它。从 17.2.6.3 开始:“使用外部链接声明的 C 标准库中的名称是否具有 extern "C"extern "C++" 链接是实现定义的。建议实现为此目的使用 extern "C++" 链接”。跨度>
【解决方案4】:

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

【讨论】:

  • 我也可以只using namespace namespace,并通过::log() 调用数学函数,同时通过log()namespace::log() 调用我的类
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-22
  • 1970-01-01
  • 2013-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多