假設我有一些就地操作的值型別。例如,像這樣:
using MyType = std::array<100, int>;
void Reverse(MyType &value) {
std::reverse(value.begin(), value.end());
}
(型別和操作可能更復雜,但關鍵是操作就地作業,并且型別可以簡單地復制和破壞。請注意,MyType 足夠大,可以避免復制,但又足夠小,它可能不會'在堆上分配是沒有意義的,因為它只包含原語,所以它不能從移動語意中受益。)
我通常發現還定義一個不會就地更改值但回傳應用了操作的副本的輔助函式很有幫助。除其他外,這可以啟用如下代碼:
MyType value = Reversed(SomeFunction());
考慮到Reverse()就地操作,邏輯上應該可以在value不復制結果的情況下進行計算SomeFunction()。如何實施 Reversed()以避免不必要的副本?如果這是啟用此優化所必需的,我愿意將 Reversed() 定義為標題中的行內函式。
我可以想到兩種方法來實作這一點:
inline MyType Reversed1(const MyType &value) {
MyType result = value;
Reverse(result);
return result;
}
這得益于回傳值優化,但只有在引數value被復制到result.
inline MyType Reversed2(MyType value) {
Reverse(value);
return value;
}
這可能需要呼叫者復制引數,除非它已經是一個右值,但我不認為以這種方式啟用回傳值優化(或者是這樣嗎?)所以回傳時有一個副本。
有沒有一種方法Reversed()可以避免復制,理想情況下是以最近的 C 標準保證的方式實作的?
uj5u.com熱心網友回復:
如果您確實想就地反轉字串,以便在呼叫站點上可以看到您作為引數發送的字串的更改,并且您還希望按值回傳它,那么您別無選擇,只能復制它。它們是兩個獨立的實體。
一種替代方法:通過參考回傳輸入值。然后它將參考您發送給函式的同一物件:
MyType& Reverse(MyType& value) { // doesn't work with r-values
std::reverse(std::begin(value), std::end(value));
return value;
}
MyType Reverse(MyType&& value) { // r-value, return a copy
std::reverse(std::begin(value), std::end(value));
return value; // no point in moving a std::array
}
另一種選擇:創建您就地回傳的物件。然后,您將回傳一個 RVO 生效的單獨實體。沒有移動或復制。不過,它將是與您發送給函式的實體不同的實體。
MyType Reverse(const MyType& value) {
// Doesn't work with `std::array`s:
return {std::rbegin(value), std::rend(value)};
}
如果std::array可以像大多數其他容器一樣從迭代器構造第二個替代方案,但它們不能。一種解決方案可能是創建一個幫助程式以確保 RVO 正常作業:
using MyType = std::array<int, 26>;
namespace detail {
template<size_t... I>
constexpr MyType RevHelper(const MyType& value, std::index_sequence<I...>) {
// construct the array in reverse in-place:
return {value[sizeof...(I) - I - 1]...}; // RVO
}
} // namespace detail
constexpr MyType Reverse(const MyType& value) {
// get size() of array in a constexpr fashion:
constexpr size_t asize = std::tuple_size_v<MyType>;
// RVO:
return detail::RevHelper(value, std::make_index_sequence<asize>{});
}
uj5u.com熱心網友回復:
您的最后一個選擇是要走的路(錯字除外):
MyType Reversed2(MyType value)
{
Reverse(value);
return value;
}
[N]RVO 不適用于return result;,但至少它是隱式移動的,而不是復制的。
根據引數的值類別,您將有一個副本 一個移動或兩個移動。
uj5u.com熱心網友回復:
有個竅門。它不漂亮,但它有效。
接受不是一個 T,而是一個回傳 T 的函式。Reversed這樣稱呼它:
MyType value = Reversed(SomeFunction); // note no `SomeFunction()`
這是 Reversed 的完整實作:
template <class Generator>
MyType Reversed(Generator&& g)
{
MyType t{g()};
reverse(t);
return t;
}
這不會產生復制或移動。我檢查了。
如果您覺得特別討厭,請執行此操作
#define Reversed(x) Reversed([](){return x;})
并回傳呼叫Reversed(SomeFunction())。同樣,沒有復制或移動。如果您設法通過公司代碼審查來獲得獎勵積分。
uj5u.com熱心網友回復:
您可以使用輔助方法將您的就地操作變成可以在 Rvalues 上作業的東西。當我在 GCC 中對此進行測驗時,它會導致一次移動操作,但沒有副本。該模式如下所示:
void Reversed(MyType & m);
MyType Reversed(MyType && m) {
Reversed(m);
return std::move(m);
}
這是我用來測驗此模式是否會導致復制的完整代碼:
#include <stdio.h>
#include <string.h>
#include <utility>
struct MyType {
int * contents;
MyType(int value0) {
contents = new int[42];
memset(contents, 0, sizeof(int) * 42);
contents[0] = value0;
printf("Created %p\n", this);
}
MyType(const MyType & other) {
contents = new int[42];
memcpy(contents, other.contents, sizeof(int) * 42);
printf("Copied from %p to %p\n", &other, this);
}
MyType(MyType && other) {
contents = other.contents;
other.contents = nullptr;
printf("Moved from %p to %p\n", &other, this);
}
~MyType() {
if (contents) { delete[] contents; }
}
};
void Reversed(MyType & m) {
for (int i = 0; i < 21; i ) {
std::swap(m.contents[i], m.contents[41 - i]);
}
}
MyType Reversed(MyType && m) {
Reversed(m);
return std::move(m);
}
MyType SomeFunction() {
return MyType(7);
}
int main() {
printf("In-place modification\n");
MyType x = SomeFunction();
Reversed(x);
printf("%d\n", x.contents[41]);
printf("RValue modification\n");
MyType y = Reversed(SomeFunction());
printf("%d\n", y.contents[41]);
}
我不確定標準是否保證了這種缺乏副本,但我認為是這樣,因為有些物件是不可復制的。
注意:最初的問題只是關于如何避免復制,但我擔心球門柱正在改變,現在我們正試圖避免復制和移動。我提出的右值函式似乎確實執行了一次移動操作。但是,如果我們不能消除移動操作,我建議 OP 重新設計他們的類,以便移動便宜,或者干脆放棄這種更短語法的想法。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/493779.html
標籤:C
