簡體   English   中英

當使用返回值分配給的變量調用 function 時,C++ 返回值優化 (RVO) 如何工作?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM