提问人:user3188445 提问时间:7/2/2022 最后编辑:Remy Lebeauuser3188445 更新时间:9/1/2023 访问量:269
命名空间 std 中不可寻址函数的基本原理是什么?
What is the rationale for non-addressable functions in namespace std?
问:
[namespace.std] 不允许获取命名空间中大多数函数的地址或引用。这是一个很大的陷阱,因为它似乎经常将标准库函数作为参数传递,即使这可能会停止工作,或者更糟的是,在不同的编译器上。std
据推测,这样做是为了让实现专门优化标准库。此限制使 C++ 更难使用。
您能否给出明确的示例来说明 C++ 实现如何从命名空间的这种限制中受益?std
如果这些优化如此重要,以至于需要使C++更难使用,为什么一些非系统库不需要同样的东西呢?
答:
首先,值得注意的是,这种设计并非起源于 C;这在 C++ 中是全新的。
出于相同的语法原因,允许采用库函数的地址,即使它也被定义为宏。89
89 这意味着实现必须为每个库函数提供一个实际函数,即使它也为该函数提供了一个宏。
但是,由于多种原因,这些严格的保证在 C++ 中将非常严格。 作为免责声明,我无法找到 Bjarne 本人的引述,所以我要说的一切都是社区共识和个人经验的集合。
1. 添加重载可能会破坏源兼容性
假设你有一个函数:
bool is_even(int x) { return x % 2 == 0; }
最初调用 可能是安全的,但如果除了 之外还添加了 和 的重载,那么 的用法将变得格式不正确。std::partition(begin, end, is_even)
long
long long
int
is_even
从本质上讲,任何可寻址函数将来都不能接收额外的重载,因为它会破坏现有代码。这就是为什么 [namespace.std] 特别说“可能格式不正确”的原因。
2. 签名比 C 语言更容易更改
破坏兼容性的另一种方法是使现有函数更加通用。 例如,它允许标准库使其函数更加通用,例如转动:
// possible historical implementation in <math.h> until C++11
#define isnan(x) implementation-defined
到
bool isnan(long double); //possibly with overloads for float and double
并随后进入
bool isnan(std::floating_point auto);
借助函数重载和模板等功能,函数的实现可能会随着时间的推移而发生巨大变化。
当然,没有人能预见到数学库中的这些剧烈变化,但对不可寻址函数的限制使它们成为可能,而不会破坏任何符合要求的代码。
3. 函数可能没有地址
函数可能没有任何地址的原因有两个:
- 它是一种内在功能
- 这意味着函数调用只是告诉编译器生成一些 IR 指令,而实际函数甚至可能不存在
- 它是一个即时函数(即 C++20)
consteval
前一个原因可能是做出这一决定的重要因素。
如今,标准库中使用的任何内部函数通常都有一个函数包装器,但这在当时并不明显。inline
一个更现代的内在标准库函数的例子是,它在 MSVC STL 中被“有点内在”。
请参阅在 C++ 中提高调试性能的状态。std::move
4. “函数”可以是函数对象
在 C++ 中,也可以将函数实现为函数对象,例如:
// inline has only been added in C++17, but compilers could have supported its
// functionality before that
inline const struct {
float operator()(float x) const;
} sqrt;
如果一个函数实际上是一个函数对象,那么在获取其地址时,行为会有所不同,因为你不会得到一个函数指针。但是,调用它的行为相同(除了 ADL 不会发生)。
这是通过使函数不可寻址而实现的另一种形式的灵活性。
结论
使采用标准库函数的地址成为可能将大大降低实现者的灵活性。 几乎任何更改(例如添加重载)都会破坏兼容性,从而有效地冻结语言进度。
这在 C 中不是一个大问题,因为函数签名无论如何都会被冻结,但在 C++ 中会产生重大的负面影响。
评论
foo(1,1)
void (int,int)
void(int,int,int=0)