<cmath>
是一个有趣的标题。允许(但不要求)制作::sqrt
和
std::sqrt
同义词。如果你包含它,最好假设两者都存在(或者只是 include <math.h>
,在这种情况下,::sqrt
你应该得到的只是)。在您的情况下可能发生的情况是 1)std::sqrt
实际上是 的同义词 (via using
) ::sqrt
,并且 2) 链接器正在获取您的::sqrt
第一个,因此您最终会出现无休止的递归。除了更改名称之外,唯一的解决方案是将您
sqrt
的名称放在名称空间中。
编辑:
需要明确的是:以上是 C++11。早期版本的 C++不允许将<cmath>
任何东西引入全局命名空间。然而,所有的实现都做了,所以标准被改变以支持这种做法。(我想这是让编译器符合标准的一种方法。)
编辑:
一些关于图书馆如何“拾取”符号的附加信息,以回应评论中的问题。形式上,根据 C++ 标准,一个程序中可能没有同一函数的两个定义(相同的名称、命名空间和参数类型)。如果这两个定义在不同的翻译单元中,则行为未定义。考虑到这一点,有几个实际的考虑。
第一个可以被认为是库的定义(或至少是传统的定义)。就标准而言,库是一组模块——翻译单元。(通常,但不总是,模块由编译的目标文件组成。)然而,在库中链接并不会引入其中的所有模块。仅当库中的模块解析未解析的外部时,它才会合并到您的程序中。因此,如果::sqrt
在链接器查看库之前已经定义(解析),则包含
::sqrt
在库中的模块将不会成为您程序的一部分。
在实践中,图书馆这个术语近年来一直被滥用,以至于人们可能会说它的含义已经发生了变化。特别是,微软所说的“动态加载的库”(以及很久以前在 Unix 中被称为“共享对象”的东西)并不是传统意义上的库,上述内容并不适用于它们。但是,其他问题取决于动态加载器的工作方式。在 Unix 中,如果多个共享对象具有相同的符号,所有共享对象都将解析为第一个加载的对象(默认情况下,这可以通过传递给的选项来控制dlopen
)。在 Windows 的情况下,默认情况下,如果可能,符号将在 DLL 中解析;在你的情况下,如果std::sqrt
是一个内联函数,或者被指定为using ::sqrt
,这将是调用的 DLL
std::sqrt
; 如果在标头中是__declspec(dllexport)
,这将是包含
std::sqrt
.
最后,今天几乎所有的链接器都支持某种形式的弱引用。这通常用于模板实例化:类似的东西std::vector<int>::vector( size_t, int )
将在每个使用它的翻译单元中实例化,但作为“弱”符号。然后链接器选择一个(可能是它遇到的第一个,但没有指定),并抛出所有其他的。虽然这种技术主要用于模板实例化,但编译器可以使用弱引用定义任何函数(如果函数是内联的,也会这样做)。在这种情况下,如果定义不同(如您的情况
::sqrt
),那么我们可以真正说该程序是非法的,因为它违反了单一定义规则。但结果是未定义的行为,不需要诊断。例如,如果您在两个不同的翻译单元中以不同的方式定义内联函数或函数模板,您几乎不会出错;如果编译器实际上没有内联它们,链接器将选择一个,并在两个翻译单元中使用它。在您的情况下(::sqrt
),我怀疑这是否适用;我希望这是一个真正的库函数,而不是内联的。(如果它被内联,定义将在 header<cmath>
中,并且您会收到重复定义错误,因为两个定义都在同一个翻译单元中。)