编译器可以删除以下副本吗
Can the compiler elide the following copy?
我还是一个新手程序员,我知道过早的优化是不好的,但我也知道复制大量的东西也是不好的。
我读过关于复制省略的文章,它是同义词,但例如维基百科上的例子让我觉得,只有当要返回的对象在完全构建的同时返回时,复制省略才能发生。
向量之类的对象呢?通常只有在填充了一些东西时,当用作返回值时,它们才有意义。毕竟,空向量可以手动实例化。
那么,它在这种情况下也有效吗?
简洁风格差:
vector<foo> bar(string baz)
{
vector<foo> out;
for (each letter in baz)
out.push_back(someTable[letter]);
return out;
}
int main()
{
vector<foo> oof = bar("Hello World");
}
我使用bar(vector&out,字符串文本)并没有真正的问题,但上面的方式看起来会更好,美观,而且符合目的。
例如,维基百科上的例子让我觉得,只有当要返回的对象在完全构建的同时返回时,才能进行复制省略。
这是误导(读作:错误)。问题是,在所有代码路径中只有一个对象被返回,即潜在返回对象只有一个构造正在发生。
你的代码很好,任何现代编译器都可以删除副本。
另一方面,以下代码可能会产生问题:
vector<int> foo() {
vector<int> a;
vector<int> b;
// … fill both.
bool c;
std::cin >> c;
if (c) return a; else return b;
}
在这里,编译器需要完全构造两个不同的对象,只有之后才能决定返回其中的哪一个,因此它必须复制一次,因为它无法在目标内存位置直接构造返回的对象。
没有什么可以阻止编译器删除副本。这在12.8.15:中有定义
[…]复制操作的省略是在以下情况下允许情况(可能结合消除多个副本):
[…]
- 当具有未绑定到引用(12.2)将被复制到具有同一简历不合格类型,复印件操作可以通过构造临时对象直接进入目标省略的副本
它是否真的这样做取决于编译器和您使用的设置。
vector
的两个隐含副本都可以而且经常被消除。命名返回值优化可以消除返回语句return out;
中隐含的副本,也允许消除oof
的副本初始化中隐含的for临时。
在两种优化都在进行的情况下,在vector<foo> out;
中构造的对象与oof
是相同的对象。
使用这样的人工测试用例来测试这些优化中的哪一个更容易。
struct CopyMe
{
CopyMe();
CopyMe(const CopyMe& x);
CopyMe& operator=(const CopyMe& x);
char data[1024]; // give it some bulk
};
void Mutate(CopyMe&);
CopyMe fn()
{
CopyMe x;
Mutate(x);
return x;
}
int main()
{
CopyMe y = fn();
return 0;
}
复制构造函数已声明但未定义,因此无法内联和消除对它的调用。使用现在相对较旧的gcc 4.4进行编译会在-O3 -fno-inline
处提供以下程序集(经过过滤以分解C++名称,并经过编辑以删除非代码)。
fn():
pushq %rbx
movq %rdi, %rbx
call CopyMe::CopyMe()
movq %rbx, %rdi
call Mutate(CopyMe&)
movq %rbx, %rax
popq %rbx
ret
main:
subq $1032, %rsp
movq %rsp, %rdi
call fn()
xorl %eax, %eax
addq $1032, %rsp
ret
可以看出,没有对复制构造函数的调用。事实上,即使在-O0
,gcc也会执行这些优化。您必须提供-fno-elide-constructors
才能关闭此行为;如果这样做,那么gcc会生成两个对CopyMe
的复制构造函数的调用——一个在对fn()
的调用内部,一个在调用外部。
fn():
movq %rbx, -16(%rsp)
movq %rbp, -8(%rsp)
subq $1048, %rsp
movq %rdi, %rbx
movq %rsp, %rdi
call CopyMe::CopyMe()
movq %rsp, %rdi
call Mutate(CopyMe&)
movq %rsp, %rsi
movq %rbx, %rdi
call CopyMe::CopyMe(CopyMe const&)
movq %rbx, %rax
movq 1040(%rsp), %rbp
movq 1032(%rsp), %rbx
addq $1048, %rsp
ret
main:
pushq %rbx
subq $2048, %rsp
movq %rsp, %rdi
call fn()
leaq 1024(%rsp), %rdi
movq %rsp, %rsi
call CopyMe::CopyMe(CopyMe const&)
xorl %eax, %eax
addq $2048, %rsp
popq %rbx
ret
- 将数组的地址分配给变量并删除
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- C/C++编译器通常会删除重复的库吗
- 从链接列表c++中删除一个项目
- C++如何通过用户输入删除列表元素
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- 当类型适当的构造函数可用时,为什么一个编译器尝试使用已删除的副本构造函数
- 为什么没有为函数参数删除副本
- 如何不删除基类对象的副本?
- 当我修改、更改和“删除”原始指针时,是否需要指针副本
- ifstream的已删除副本构建器的替代方法
- 在已删除的副本分配中检测到虚假错误
- 是否可以同时从 std::vector 或 std::d eque 中删除和获取对象的副本C++
- GCC C++11 删除移动可分配类的副本分配会阻止 std::sort 编译
- 删除结构的成员,并将其重新制作为其他内容的副本,而不使用 new
- 为什么在副本分配运算符的定义中需要删除
- 使用已删除的副本构造函数和初始值设定项列表重载调用类定义中的成员构造函数
- 任何编译器真的会删除这些副本吗
- 模板化构造函数可以代替已删除的副本构造函数吗
- 编译器可以删除以下副本吗