假设someDataStruct
是“巨大的”,那么在以下两种情况下,一个现代编译器会产生等价的,因此也是同样有效的代码吗?
1) void fooByValue(const SomeDataStruct data);
2) void fooByReference(const SomeDataStruct& data);
如果等价,哪一个成语是“可取”的,为什么?
请注意,这道题与这道题类似:
https://softwareengineering.stackexchange.com/questions/372105/is-passing-arguments-as-const-references-premater-optimization
但不完全相同,因为这里我在两个函数中都使用“const
”; 在所提到的链接中,FoobyValue
只是
1) void fooByValue(SomeDataStruct data);
编辑:假设someDataStruct
类型没有任何复制构造函数。
假设某个DataStruct是“巨大的”,那么在以下两种情况下,一个现代编译器会产生等价的,因此也是同样高效的代码吗?
这取决于编译器,你是如何定义对象的,函数中的对象发生了什么。 让我们了解gcc在x86-64 Linux上的作用。 看一看下面的代码:
struct big_
{
unsigned long w,x,y,z;
int pos;
};
unsigned long byval(const big_ big)
{
auto y = big.z;
y += big.y;
y += big.x;
y += big.w;
return y;
}
unsigned long byref(const big_& big)
{
auto y = big.z;
y += big.y;
y += big.x;
y += big.w;
return y;
}
用g++-std=C++17-o3
编译它会得到以下程序集Godbolt链接:
byval(big_):
mov rax, QWORD PTR [rsp+24]
add rax, QWORD PTR [rsp+32]
add rax, QWORD PTR [rsp+16]
add rax, QWORD PTR [rsp+8]
ret
byref(big_ const&):
mov rax, QWORD PTR [rdi+16]
add rax, QWORD PTR [rdi+24]
add rax, QWORD PTR [rdi+8]
add rax, QWORD PTR [rdi]
ret
在上面的代码中我们能看到什么呢?
第一个函数byval
的参数在堆栈上传递。 第二个函数的参数通过寄存器rdi
传递(请参阅操作系统调用约定以了解原因)。 通过寄存器传递任何东西总是比通过堆栈传递更快,因为寄存器更靠近CPU
,而堆栈在缓存或RAM中的某个地方。 因此这里通过引用传递更好。 如果您有一个小对象(8字节),通过值传递它更好,因为它将通过寄存器传递。 只有当对象太大以至于无法容纳寄存器时,才会通过堆栈传递。
我们可以看到的另一件事是byval
有一个不是const
的参数。 编译器刚刚删除了它。
因为这里我在两个函数中都使用了“const”;
从上面的解释中可以看出,这并不重要。
void fooByValue(SomeDataStruct data);
edit:假设SomeDataStruct类型没有任何复制构造函数。
如果您没有编写复制构造函数,这并不意味着编译器不能隐式地为您生成一个。 但我们假设你把它删掉了。 所以现在您不能复制它,如果您尝试,您的代码将无法编译。 但是,如果您已经定义了移动构造函数,则可以移动它。
void fn()
{
fooByValue( std::move(data) ); // no copy
}
使用常量
不会更改代码的性能。 const
就像是您和编译器之间的契约。 当您将某事标记为const
时,编译器将不允许您更改它。 如果你以某种方式改变它,它将导致未定义的行为。 我建议你去读一下这篇由阿瑟·奥德怀尔写的关于const是一份合同的文章。
顶级常量
(不在指针/引用之后)在声明中不起作用。
// Even if the declaration uses const:
void fooByValue(const SomeDataStruct data);
// The definition without `const` is still considered a valid definition for that declaration.
// It's not a different overload!
// (this oddity is inherited from C, where overloading is a compiler error but this was always allowed)
void fooByValue(SomeDataStruct data)
{
data.member = 0;
}
因此编译器被迫忽略常量
,并且必须假定参数可能被修改-->; 副本是必要的。 但是,如果在函数调用之后没有使用原始的copy-from变量,编译器仍然可能优化出副本。
C++标准的相关部分是9.3.3.5函数:
函数的类型是使用以下规则确定的。 [..]在生成参数类型列表之后,在形成函数类型时删除修改参数类型的任何顶级CV限定符。