c++ - 用返回值分配给变量的函数调用该函数时,C++返回值优化(RVO)如何工作?

标签 c++ return-value-optimization

最初,我遇到这样的问题:我有一个带有数据的 vector ,并且想执行一次n次操作。在原位执行此操作是不可能的,因此在每个循环周期中都会构造一个新的 vector ,完成操作并释放内存。哪种操作对我的问题并不重要,但从数学上讲,它平方排列的平方(我只是想提出一个无法就地完成的操作)。它将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;
}
请不要在您的计算机上尝试,结果可能会有所不同。该程序具有一个简单的数据对象,该对象显示何时调用构造函数和析构函数。我正在使用针对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
如您所见,对象1永不被破坏。我认为这是由于RVO而发生的。 RVO将新的对象1构造到对象0的位置。但是,由于RVO忘记制作对象0的临时副本,因此使用索引1调用了析构函数get。
将索引声明为const有助于防止此错误,因为编译器会引发错误。
object of type 'RegisteredObject' cannot be assigned because its copy
      assignment operator is implicitly deleted
但是我不认为这是解决方案。对我来说,似乎C++(或至少是clang)的RVO已损坏。这意味着,上述置换的实现可能会尝试使内存释放两次,或者根本不使用RVO。
因此,首先,您认为导致Bug的原因是没有释放对象1?
您将如何实现sqarePermutationNTimes方法的高效且美观的版本?

最佳答案

当监视构造函数/析构函数时,不要忘了copy(/ move)构造函数和赋值,那么您将获得类似于以下内容的信息:

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是因为您没有复制构造函数调用。
但是分配仍然存在。
a = objectWithIncreasedIndex(a);
相当于
a = RegisteredObject(a.getIndex() + 1);
代替
a = RegisteredObject(RegisteredObject(a.getIndex() + 1));
感谢RVO。
对于第一个代码段,您已经创建了n(移动)分配和n临时对象。
您可以通过使用(旧的)输出变量方式来减少临时工。
// 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;
}

关于c++ - 用返回值分配给变量的函数调用该函数时,C++返回值优化(RVO)如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63983995/

相关文章:

c# - 这是什么日期和时间格式?

c++ - 电源功能未按预期工作

c++ - 什么是复制省略和返回值优化?

c++ - 什么是复制省略和返回值优化?

c++ - 为什么调用复制构造函数而不是 move 构造函数?

c++ - 为什么 std::move 会阻止 RVO(返回值优化)?

c++ - 使用openmp时出现编译错误

c++ - Qt检查文件签名有效性

c++ - 如何以编程方式获取系统偏好设置中设置的 macOS 键盘快捷键?

c++ - 如何使用条件表达式返回对象指针?