在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 之类的功能(仅执行强制转换),它们必须总是内联的(因此不会生成任何机器代码)?换句话说,(pedantic)编译器是否有可能将它们视为普通函数(即,将参数放在堆栈上,并生成 call 和 ret 指令) ?
答案 0 :(得分:2)
我的问题是:标准中是否有保证可以保证对于
std::move
或std::forward
之类的函数(仅能进行强制转换),必须始终对其进行内联(因此,不存在任何机器代码)生成)?
不。该标准停止描述抽象机的可观察行为。代码生成是标准不了解的实现细节。
话虽这么说,std::forward
和std::move
都是只真正影响C ++类型系统的操作,而不影响实际数据,所以我非常惊讶地看到为它们生成的任何机器代码优化版本。
另一方面,在未优化的构建中,保留std::move
的轮廓(几乎与其他任何函数调用一样)可能是简化调试的一个好主意。您可以轻松地对此进行测试(live on gcc.godbolt):
#include <utility>
struct Foo {
int i;
Foo(Foo &&other) :i(other.i) {};
};
Foo with_move(Foo f) {
return std::move(f);
}
在带有-O0 std::move
的gcc中,它是作为实际函数生成的(除了设置/删除堆栈帧并返回接收到的指针参数外,什么也没做)
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
答案 1 :(得分:0)
这不是来自标准,而是来自Scott Meyer的有效CPP。摘录
第30项:了解内联的来龙去脉。
编译器优化是 通常是为缺少函数调用的代码段而设计的,因此 内联函数时,可以使编译器执行上下文- 功能主体上的特定优化。大多数编译器 永远不要在“概述”的函数调用上执行此类优化。
...
在内存有限的机器上,过于热情 内联会导致程序太大而无法使用 空间。即使有了虚拟内存,内联导致的代码膨胀也会导致 进行额外的分页,降低指令高速缓存命中率以及 这些事情伴随着性能损失。
...
另一方面,如果内联函数体非常短,则代码 为函数主体生成的代码可能小于生成的代码 用于函数调用。在这种情况下,内联函数可能会 实际上导致较小的目标代码和较高的指令缓存命中率 率!
...
请记住,内联是对编译器的请求,而不是命令。 ...
模板实例化与内联无关。如果您正在撰写 模板,并且您相信从 应该内联模板,声明内联模板;
...
但是,如果您为不需要内联的函数编写模板,请避免声明内联模板(显式或隐式)。内联 有成本,而您不想在没有深思熟虑的情况下产生成本。
...
... 让我们观察一下,内联是一个请求 编译器可能会忽略。大多数编译器拒绝内联函数 它们认为过于复杂(例如,包含循环或递归的循环), 除了最琐碎的虚函数调用之外,其他所有函数都无法内联。所有这些加起来:给定的内联函数是否实际上是 内联取决于您使用的构建环境,主要是 编译器。幸运的是,大多数编译器的诊断级别 如果他们无法内联函数,将导致警告 您已要求他们这样做。
有时,编译器会为内联函数生成函数主体 即使他们完全愿意内联函数。例如, 如果您的程序采用内联函数的地址,则编译器 通常必须为其生成轮廓函数体。
如果您可以掌握这本书,请阅读整本书,这将消除您的许多疑问。