c++ 参数作为 const T vs const T&



假设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-premature-optimization

但并不完全相同,因为我在这里使用";CCD_ 2";在两种功能中;在上述链路中,fooByValue只是

1) void fooByValue(SomeDataStruct data);

edit:假设SomeDataStruct类型没有任何复制构造函数。

假设SomeDataStruct是"巨大";,在以下两种情况下,现代编译器会产生等效的、因此同样有效的代码吗?

这取决于编译器、如何定义对象以及函数中对象发生了什么。让我们了解一下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传递(请参阅操作系统的调用约定以了解原因(。通过寄存器传递任何东西总是比通过堆栈传递快,因为当堆栈在缓存或ram中的某个位置时,寄存器更接近cpu。因此,此处以引用方式传递更好。如果你有一个小对象(8字节(,通过值传递它会更好,因为它无论如何都会通过寄存器传递。只有当对象太大以至于无法放入寄存器时,才会发生通过堆栈的情况。

我们可以看到的另一件事是byval有一个不是const的参数。编译器刚刚把它丢了。

因为我在这里使用";const";在两种功能中;

从上面的解释中可以看出,这并不重要。

void fooByValue(SomeDataStruct data);edit:假设SomeDataStruct类型没有任何复制构造函数。

如果您没有编写复制构造函数,这并不意味着编译器不能隐式为您生成一个。但假设你删除了它。所以现在你不能复制它,如果你尝试,你的代码将不会编译。然而,如果你已经定义了一个移动构造函数,你可以移动它。

void fn()
{
fooByValue( std::move(data) ); // no copy
}

使用const不会更改代码的性能。const就像您和编译器之间的一份合同。当你将某个东西标记为const时,编译器不允许你更改它。如果你以某种方式更改它,它将导致未定义的行为。我建议你去阅读Arthur O'Dwyer关于const is a contract的这篇文章。

顶级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;
}

因此,编译器被迫忽略const,并且必须假定参数可能被修改-->复印件是必要的。但是,如果在函数调用后没有使用原始的从变量复制的,编译器可能仍然会优化副本。

C++标准的相关部分是9.3.3.5函数:

函数的类型是使用以下规则确定的。[..]生成参数类型列表后,在形成函数类型时,任何修改参数类型的顶级cv限定符都将被删除。

最新更新