单行函数模板 c++ 的内联性保证
Inlineness guarantee of one-liner function templates c++
C++标准库中,有许多单行函数模板。 例如std::move 本质上只是一个强制转换,实现可以是:
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
我知道实际上,不会从std::move生成机器代码,因为它只是一个强制转换。我的问题是:标准中是否有任何保证,对于像 std::move或std::forward这样的函数(只做转换(,它们必须始终是内联的(所以不会生成机器代码(?换句话说,(迂腐的(编译器是否可以将它们视为普通函数(即将参数放在堆栈上,并生成调用和ret指令(?
我的问题是:标准中是否有任何保证,对于像
std::move
或std::forward
这样的函数(只做强制转换(,它们必须始终是内联的(所以不会生成机器代码(?
不。该标准止步于描述抽象机器的可观察行为。代码生成是一个实现细节,标准对此一无所知。
话虽如此,std::forward
和std::move
都是实际上只影响C++类型系统而不是实际数据的操作,因此,如果在优化的构建中看到为它们生成的任何机器代码,我会非常惊讶。
另一方面,在未优化的版本中,保留概述std::move
(与几乎任何其他函数调用一样(可能是简化调试的好主意。你可以很容易地测试这个(在gcc.godbolt上直播(:
#include <utility>
struct Foo {
int i;
Foo(Foo &&other) :i(other.i) {};
};
Foo with_move(Foo f) {
return std::move(f);
}
在 gcc 中,带有 -O0std::move
作为实际函数生成(除了设置/拆除堆栈帧并返回它收到的指针参数外,它什么都不做(
Foo::Foo(Foo&&):
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov QWORD PTR [rbp-16], rsi
mov rax, QWORD PTR [rbp-16]
mov edx, DWORD PTR [rax]
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], edx
nop
pop rbp
ret
with_move(Foo):
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov QWORD PTR [rbp-16], rsi
mov rax, QWORD PTR [rbp-16]
mov rdi, rax
call std::remove_reference<Foo&>::type&& std::move<Foo&>(Foo&)
mov rdx, rax
mov rax, QWORD PTR [rbp-8]
mov rsi, rdx
mov rdi, rax
call Foo::Foo(Foo&&)
mov rax, QWORD PTR [rbp-8]
leave
ret
std::remove_reference<Foo&>::type&& std::move<Foo&>(Foo&):
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
pop rbp
ret
而即使在 -O1 时,所有内容也会内联:
with_move(Foo):
mov rax, rdi
mov edx, DWORD PTR [rsi]
mov DWORD PTR [rdi], edx
ret
这不是来自标准,而是来自斯科特迈耶的有效CPP。 第 30 项的一些
摘录:了解内联的来龙去脉。
编译器优化是 通常设计用于缺少函数调用的代码段,因此 当您内联函数时,您可以使编译器执行上下文- 对函数主体的特定优化。大多数编译器 切勿对"概述"函数调用执行此类优化。
。
在内存有限的机器上,过分热心 内联可能会导致程序太大而无法使用 空间。即使使用虚拟内存,内联引起的代码膨胀也可能导致 到额外的分页,降低指令缓存命中率,以及 伴随这些事情的绩效处罚。
。
另一方面,如果内联函数体非常短,则代码 为函数体生成的代码可能小于生成的代码 用于函数调用。如果是这种情况,内联函数可能会 实际上会导致更小的目标代码和更高的指令缓存命中 率!
。
请记住,内联是对编译器的请求,而不是命令。 ...
模板实例化与内联无关。如果你正在写一个 模板,您认为所有函数都从 模板应该是内联的,声明模板是内联的;
。
但是,如果您正在为没有理由想要内联的函数编写模板,请避免以内联方式(显式或隐式(声明模板。内联 有成本,你不想在没有深思熟虑的情况下产生它们。
。
。让我们完成内联是一个请求的观察 编译器可能会忽略。大多数编译器拒绝内联函数 他们认为太复杂了(例如,那些包含循环或递归的(, 除了对虚函数的最微不足道的调用之外,所有调用都无视内联。这一切加起来就是:给定的内联函数是否真的是 内联取决于您使用的构建环境 — 主要基于 编译器。幸运的是,大多数编译器的诊断级别是 如果它们无法内联函数,将导致警告 你已经要求他们这样做了。
有时编译器会为内联函数生成函数体 即使他们完全愿意内联函数。例如 如果您的程序采用内联函数的地址,编译器 通常必须为其生成概述的函数体。
如果你能拿到这本书,阅读整篇文章,它会消除你的许多疑虑。
- 当函数模板参数是具有默认参数的类模板时,函数模板参数的推导如何执行
- 将重载的成员函数传递给函数模板
- C++17中函数模板中的静态数组初始化(MSVC 2019)
- 为什么 gcc 和 clang 为函数模板的实例化生成不同的符号名称?
- 具有常量引用参数的函数模板专用化
- std::span<const T> 作为函数模板中的参数
- 如何编写一个完美的缩写函数模板?
- 仅在函数模板中为那些定义了函数的类型执行函数
- 如何在C++中伪造虚拟可变参数函数模板?
- 以下代码中的函数模板有什么问题?
- 在 C++20 中是否不再允许在 std 中对程序定义类型的函数模板进行专用化?
- 将显式实例化的函数模板与转换匹配
- 使用定义函数模板别名
- 函数模板返回类型
- C++有什么方法可以在既不调用函数模板也不提供其模板参数的情况下引用函数模板?
- C++ std::functional 中的可变参数函数模板
- 单行函数模板 c++ 的内联性保证
- C++函数模板需要 &for 数组参数
- 概念解析为使用 std::make_signed_t 时意外的函数模板
- 两个函数模板候选项.将一个参数作为引用后,选择不太专业的模板