[英]How does C++ return value optimization (RVO) work when the function get's called with the variable that the return value gets assigned to?
最初,我有這樣一個問題:我有一個帶有數據的向量,想要執行 n 次操作。 就地執行它是不可能的,因此在每個循環周期中都會構造一個新向量,操作完成並釋放 memory。 什么樣的操作對我的問題並不重要,但在數學上它是排列的平方(我只是想提出一個不能就地完成的操作)。 它對所有元素應用result[i] = in[in[i]]
。
vector<int> *sqarePermutationNTimesUnsafe(vector<int> *in, int n)
{
if (n <= 0) { throw; }
vector<int> *result = in;
vector<int> *shuffled;
for (int i = 0; i < n; i++)
{
shuffled = new vector<int>(in->size(), 0);
for (int j = 0; j < in->size(); j++)
{
(*shuffled)[j] = (*result)[(*result)[j]];
}
if (result != in) { delete result; }
result = shuffled;
}
return result;
}
加速的主要事情是,新數據被寫入shuffled
,然后只需要一個指針交換來進行下一次洗牌。 它有效,但它很丑陋且容易出錯。 所以我想出一個更好的方法是使用現代 c++。傳遞引用應該比傳遞vectors<...>
更快,所以我這樣做了。 由於不能直接交換引用,我將其拆分為兩個函數並依靠返回值優化來交換引用。
// do the permutation
vector<int> sqarePermutation(const vector<int> &in)
{
vector<int> result(in.size(), 0);
for (int i = 0; i < in.size(); i++)
{
result[i] = in[in[i]];
}
return result;
}
// do the permutation n times
vector<int> sqarePermutationNTimes(const vector<int> &in, const int n)
{
vector<int> result(in); // Copying here is ok and required
for (int i = 0; i < n; i++)
{
result = sqarePermutation(result); // Return value optimization should be used so "swap" the references
}
return result;
}
我想確定 RVO 是否有效,所以我編寫了一個小程序來測試它。
#include <iostream>
using namespace std;
class RegisteredObject
{
private:
int index;
public:
RegisteredObject(int index);
~RegisteredObject();
int getIndex() const;
};
RegisteredObject::RegisteredObject(int index): index(index)
{
cout << "- Register Object " << index << endl;
}
RegisteredObject::~RegisteredObject()
{
cout << "- Deregister Object " << index << endl;
}
int RegisteredObject::getIndex() const
{
return index;
}
RegisteredObject objectWithIncreasedIndex(const RegisteredObject &object)
{
return RegisteredObject(object.getIndex() + 1);
}
int main() {
cout << "Init a(0)" << endl;
RegisteredObject a(0);
cout << "RVO from a to a" << endl;
a = objectWithIncreasedIndex(a); // Seems to be buggy
cout << "RVO from a to b" << endl;
RegisteredObject b = objectWithIncreasedIndex(a); // Why does a get destructed here?
cout << "End" << endl;
return 0;
}
不要猶豫,在您的機器上嘗試一下,結果可能會有所不同。 該程序有一個簡單的數據 object,顯示何時調用構造函數和析構函數。 我正在使用針對 x86_64-apple-darwin19.6.0 的 Mac OS Clang 11.0.3。 它產生以下結果:
Init a(0)
- Register Object 0
RVO from a to a
- Register Object 1
- Deregister Object 1 //EDIT: <--- this should be Object 0
RVO from a to b
- Register Object 2
End
- Deregister Object 2
- Deregister Object 1
如您所見,Object 1 永遠不會被破壞。 我認為這是因為 RVO。 RVO 將新的 Object 1 構造到 Object 0 的位置。但是因為它忘記制作 Object 0 的臨時副本,所以使用索引 1 調用析構函數 get。
將索引聲明為 const 有助於防止此錯誤,因為編譯器會拋出錯誤。
object of type 'RegisteredObject' cannot be assigned because its copy
assignment operator is implicitly deleted
但我不認為這是解決方案。 對我來說,C++(或至少是 clang 的)RVO 似乎已損壞。 這意味着,上面的 Permutation 實現可能會嘗試 double-free memory,或者根本不使用 RVO。
那么首先,您認為是什么導致了沒有釋放 Object 1 的 Bug?
您將如何實現高效且美觀的sqarePermutationNTimes
方法版本?
當監視構造函數/析構函數時不要忘記復制(/移動)構造函數和賦值,然后你會得到類似的東西:
Init a(0)
- Register Object {0x7ffc74c7ae08: 0}
RVO from a to a
- Register Object {0x7ffc74c7ae0c: 1}
assign {0x7ffc74c7ae08: 0} <- {0x7ffc74c7ae0c: 1}
- Deregister Object {0x7ffc74c7ae0c: 1}
RVO from a to b
- Register Object {0x7ffc74c7ae0c: 2}
End
- Deregister Object {0x7ffc74c7ae0c: 2}
- Deregister Object {0x7ffc74c7ae08: 1}
由於您沒有復制構造函數調用,因此應用了 RVO。
但分配仍然存在。
a = objectWithIncreasedIndex(a);
相當於
a = RegisteredObject(a.getIndex() + 1);
代替
a = RegisteredObject(RegisteredObject(a.getIndex() + 1));
感謝 RVO。
對於您的第一個片段,您創建了n
(移動)分配和n
臨時對象。
您可以使用(舊的)output 變量方式減少臨時變量。
// do the permutation
void squarePermutation(const std::vector<int> &in, std::vector<int>& result)
{
result.resize(in.size());
for (int i = 0; i != in.size(); ++i) {
result[i] = in[in[i]];
}
}
// Convenient call
std::vector<int> squarePermutation(const std::vector<int> &in)
{
std::vector<int> result;
squarePermutation(in, result);
return result;
}
// do the permutation n times
vector<int> sqarePermutationNTimes(const vector<int> &in, const int n)
{
std::vector<int> result(in); // Copying here is ok and required
std::vector<int> tmp; // outside of loop so build only once
for (int i = 0; i != n; i++)
{
squarePermutation(result, tmp);
std::swap(result, tmp); // Modify internal pointers
}
return result;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.