[英]c++ parameter as const T vs const T&
假設SomeDataStruct
是“巨大的”,現代編譯器會在以下兩種情況下產生等效且同樣有效的代碼嗎?
1) void fooByValue(const SomeDataStruct data);
2) void fooByReference(const SomeDataStruct& data);
如果等效,哪個成語是“首選”,為什么?
請注意,這個問題與這個問題相似:
但不完全相同,因為我在這兩個函數中都使用了“ const
”; 在提到的鏈接中, fooByValue
只是
1) void fooByValue(SomeDataStruct data);
編輯:假設SomeDataStruct
類型沒有任何復制構造函數。
頂級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
並且必須假設參數可能被修改--> 副本是必要的。 但是,如果在 function 調用之后未使用原始復制自變量,編譯器可能仍會優化副本。
C++ 標准的相關部分是 9.3.3.5 功能:
使用以下規則確定 function 的類型。 [..] 生成參數類型列表后,在形成 function 類型時,會刪除任何修改參數類型的頂級 cv 限定符。
假設 SomeDataStruct 是“巨大的”,現代編譯器會在以下兩種情況下產生等效且同樣有效的代碼嗎?
這取決於編譯器,您如何定義 object,function 中的 object 發生了什么。 讓我們看看 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
在上面的代碼中我們可以看到什么?
arguments 到第一個 function, byval
在堆棧上傳遞。 arguments 到第二個 function 通過寄存器rdi
傳遞(請參閱操作系統的調用約定以找出原因)。 通過寄存器傳遞任何東西總是比在堆棧上傳遞更快,因為寄存器更靠近cpu
,而堆棧位於緩存或內存中的某個位置。 因此在這里通過引用傳遞更好。 如果你有一個小的 object(8 字節),通過值傳遞它會更好,因為它無論如何都會通過寄存器傳遞。 僅當您的 object 太大以至於無法放入寄存器時,才會發生堆棧傳遞。
我們可以看到的另一件事是byval
有一個不是const
的參數。 編譯器剛剛刪除了它。
因為我在這兩個函數中都使用了“const”;
從上面的解釋中可以看出,這無關緊要。
void fooByValue(SomeDataStruct data);
編輯:假設 SomeDataStruct 類型沒有任何復制構造函數。
如果您沒有編寫復制構造函數,這並不意味着編譯器不能為您隱式生成一個。 但是讓我們假設你刪除了它。 所以現在你不能復制它,如果你嘗試,你的代碼將無法編譯。 但是,如果您定義了移動構造函數,則可以移動它。
void fn()
{
fooByValue( std::move(data) ); // no copy
}
使用const
不會改變代碼的性能。 const
就像您和編譯器之間的合同。 當您將某些內容標記為const
時,編譯器將不允許您更改它。 如果您以某種方式更改它,它將導致未定義的行為。 我建議您閱讀 go 並閱讀 Arthur O'Dwyer 關於此主題的這篇文章const is a contract。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.