【问题标题】:Why are some functions in <cmath> not in the std namespace?为什么 <cmath> 中的某些函数不在 std 命名空间中?
【发布时间】:2012-06-18 15:21:53
【问题描述】:

我正在开发一个使用多种算术类型的项目。所以我做了一个标题,其中定义了用户定义的算术类型的最低要求:

user_defined_arithmetic.h:

typedef double ArithmeticF;   // The user chooses what type he 
                              // wants to use to represent a real number

namespace arithmetic          // and defines the functions related to that type
{

const ArithmeticF sin(const ArithmeticF& x);
const ArithmeticF cos(const ArithmeticF& x);
const ArithmeticF tan(const ArithmeticF& x);
...
}

困扰我的是,当我使用这样的代码时:

#include "user_defined_arithmetic.h"

void some_function()
{
    using namespace arithmetic;
    ArithmeticF lala(3);
    sin(lala);
}

我得到一个编译器错误:

error: call of overloaded 'sin(ArithmeticF&)' is ambiguous
candidates are:
double sin(double)
const ArithmeticF arithmetic::sin(const ArithmeticF&)

我从未使用过&lt;math.h&gt; 标头,只使用了&lt;cmath&gt;。我从未在头文件中使用过using namespace std

我正在使用 gcc 4.6.*。我检查了包含模棱两可声明的标题是什么,结果是:

ma​​thcalls.h:

Prototype declarations for math functions; helper file for <math.h>.
...

我知道,&lt;cmath&gt; 包括 &lt;math.h&gt;,但它应该屏蔽 std 命名空间的声明。我深入研究&lt;cmath&gt; 标头并找到:

cmath.h:

...

#include <math.h>

...

// Get rid of those macros defined in <math.h> in lieu of real functions.
#undef abs
#undef div
#undef acos
...

namespace std _GLIBCXX_VISIBILITY(default)
{
...

所以命名空间std #include &lt;math.h&gt; 之后开始。这里有什么问题,还是我误会了什么?

【问题讨论】:

  • 您可能需要重新考虑一些事情:当使用算术类型(整数类型 + 双精度 + 浮点数)时,按值传递通常比按引用传递更有效(也更常见)。调用您想要特定版本的函数时,请限定调用,而不是添加using namespace X。或者,您可以使用 using 指令 (using arithmetic::sin)。最后,通过编辑 typedef 来更改类型的整个方法是一个非常糟糕的主意。
  • @DavidRodriguez-dribeas:谢谢!拜托,你能提示我一个替代解决方案吗?我使用的是通过引用传递,因为数字可以是自定义类型。这意味着它甚至可以达到几千字节。我希望当我内联函数并在内联中使用 std 基本函数时,不会造成任何伤害。还是会?请给我一些建议好吗?
  • @DavidRodriguez-dribeas:我知道 C++ 方法是声明一个抽象类,但是当您使用内置类型时,我用于矩阵计算的库会使用显着的优化。我只是不想失去这个优势
  • 你的实现可以是一个只使用特性的模板(不管是不是一个类是一个不同的问题,如果你问我免费函数很好)。您的算法 将适用于任何算术 类型,并且用户代码可以决定本地 使用哪种类型,而无需修改typedef。也就是说,用户可以知道他们正在使用的类型,而无需在标题中搜索typedef。此外,您将能够在单个应用程序中使用多种类型。
  • 另请参阅 Red Hat 博客中 Jonathan Wakely 的 Why < cstdlib > is more complicated than you might think。 Wakely 是维护 GNU 的 C++ 标准库的 GCC 开发人员之一。他专门讨论了数学函数及其可能引起的麻烦。简而言之,C++ 有重载,所以有三个abs、三个log 等。重载是intdoublelong double 以满足您的需要。但是 C 只有一个函数。

标签: c++ namespaces cmath


【解决方案1】:

允许 C++ 标准库的实现在全局命名空间以及 std 中声明 C 库函数。有些人会称这是一个错误,因为(正如您所发现的)命名空间污染会导致与您自己的名称发生冲突。然而,事情就是这样,所以我们必须忍受它。你只需要将你的名字限定为arithmetic::sin

用标准(C++11 17.6.1.2/4)的话来说:

然而,在 C++ 标准库中,声明(除了 在 C 中定义为宏的名称在命名空间 std 的命名空间范围 (3.3.6) 内。 是 未指定这些名称是否首先在全局命名空间范围内声明然后注入 通过显式使用声明 (7.3.3) 进入命名空间 std。

【讨论】:

  • 更糟糕的是std::exp 有浮点、双精度和整数的重载,而exp 仅适用于双精度。因此,using namespace std; 声明可以改变结果!
【解决方案2】:

如果您真的愿意,您可以随时围绕cmath 编写一个小包装器,如下所示:

//stdmath.cpp
#include <cmath>
namespace stdmath
{
    double sin(double x)
    {
        return std::sin(x);
    }
}

//stdmath.hpp
#ifndef STDMATH_HPP
#define STDMATH_HPP
namespace stdmath {
    double sin(double);
}
#endif

//uses_stdmath.cpp
#include <iostream>
#include "stdmath.hpp"

double sin(double x)
{
    return 1.0;
}

int main()
{
    std::cout << stdmath::sin(1) << std::endl;
    std::cout << sin(1) << std::endl;
}

我想额外的函数调用可能会产生一些开销,具体取决于编译器的聪明程度。

【讨论】:

  • 这并没有完全解决问题,考虑namespace mylib{ double sin(double x) { return 1.0; } } int main() { using namespace mylib; std::cout &lt;&lt; stdmath::sin(1) &lt;&lt; std::endl; std::cout &lt;&lt; sin(1) &lt;&lt; std::endl; }你仍然得到一个“模糊调用错误”。
  • @alfC 不,你不知道。这个答案的重点是在stdmathcpp 文件中包含&lt;cmath&gt; 而不是hpp 标头。
【解决方案3】:

这只是开始解决这个问题的一个不起眼的尝试。 (欢迎提出建议。)

我已经处理这个问题很长时间了。一个case 问题很明显就是这个case:

#include<cmath>
#include<iostream>

namespace mylib{
    std::string exp(double x){return "mylib::exp";}
}

int main(){
    std::cout << std::exp(1.) << std::endl; // works
    std::cout << mylib::exp(1.) << std::endl; // works

    using namespace mylib;
    std::cout << exp(1.) << std::endl; //doesn't works!, "ambiguous" call
    return 0;
}

在我看来,这是一个令人讨厌的错误,或者至少是一个非常不幸的情况。 (至少在 GCC 中,以及在 Linux 中使用 GCC 库的 clang。)

最近我再次尝试解决这个问题。通过查看cmath(来自 GCC),似乎标头只是为了重载 C 函数并在过程中搞砸命名空间。

namespace std{
   #include<math.h>
}
//instead of #include<cmath>

有了它就可以了

using namespace mylib;
std::cout << exp(1.) << std::endl; //now works.

我几乎可以肯定这并不完全等同于#include&lt;cmath&gt;,但大多数功能似乎都可以工作。

最糟糕的是,最终某些依赖库最终会#inclulde&lt;cmath&gt;。为此,我还没有找到解决方案。

注意:不用说这根本不起作用

namespace std{
   #include<cmath> // compile errors
}

【讨论】:

  • 声明namespace std中的任何东西都是UB。
猜你喜欢
  • 1970-01-01
  • 2016-02-01
  • 2014-05-20
  • 2014-02-25
  • 2012-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多