首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >当使用返回值赋值的变量调用函数get时,C++返回值优化(RVO)是如何工作的?

当使用返回值赋值的变量调用函数get时,C++返回值优化(RVO)是如何工作的?
EN

Stack Overflow用户
提问于 2020-09-21 05:50:23
回答 1查看 217关注 0票数 1

最初,我有一个这样的问题:我有一个包含数据的向量,并且想要执行n次操作。就地完成它是不可能的,所以在每个循环周期中都会构造一个新的向量,操作完成并释放内存。对于我的问题,什么样的操作并不重要,但从数学上讲,它是一个排列的平方(我只想想出一个不能就地完成的操作)。它将result[i] = in[in[i]]应用于所有元素。

代码语言:javascript
复制
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<...>更快,所以我这样做了。由于不能直接交换引用,因此我将其拆分为两个函数,并依靠返回值优化来交换引用。

代码语言:javascript
复制
// 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正常工作,所以我写了一个小程序来测试它。

代码语言:javascript
复制
#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;
}

不要犹豫在你的机器上尝试它,结果可能会有所不同。该程序有一个简单的数据对象,它显示构造函数和析构函数何时被调用。我使用的是针对x86_64-apple-darwin19.6.0的MacOSClang11.0.3。它会产生以下结果:

代码语言:javascript
复制
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

如你所见,对象1永远不会被析构。我认为这是因为RVO造成的。RVO将新对象1构造到对象0的位置。但是因为忘记了创建对象0的临时副本,所以析构函数get使用索引1来调用。

将索引声明为const有助于防止此错误,因为编译器会抛出错误。

代码语言:javascript
复制
object of type 'RegisteredObject' cannot be assigned because its copy
      assignment operator is implicitly deleted

但我不认为这是解决方案。对我来说,似乎C++的RVO (或者至少clang的) RVO被破坏了,这意味着上面的置换实现可能会尝试双倍释放内存,或者根本不使用RVO。

那么首先,你认为是什么原因导致Bug没有释放Object 1?

如何实现一个高效而漂亮的sqarePermutationNTimes方法呢?

EN

回答 1

Stack Overflow用户

发布于 2020-09-21 07:10:27

在监控构造函数/析构函数时,不要忘记复制(/move)构造函数和赋值,然后你会得到类似于:

代码语言:javascript
复制
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}

Demo

应用RVO是因为您没有复制构造函数调用。

但是赋值仍然存在。

代码语言:javascript
复制
a = objectWithIncreasedIndex(a);

等同于

代码语言:javascript
复制
a = RegisteredObject(a.getIndex() + 1);

而不是

代码语言:javascript
复制
a = RegisteredObject(RegisteredObject(a.getIndex() + 1));

多亏了RVO。

对于您的第一个代码片段,您已经创建了so n (move-)assignments和n temporaries。

您可以通过使用(旧的)输出变量的方式来减少临时变量。

代码语言:javascript
复制
// 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;
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63983995

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档