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

问题描述

最初,我遇到这样的问题:我有一个带有数据的向量,并且想执行一次n次操作。在原位执行此操作是不可能的,因此在每个循环周期中都会构造一个新的向量,完成操作并释放内存。对于我的问题,哪种操作并不重要,但从数学上讲,它平方排列的平方(我只是想提出一个无法就地完成的操作)。它将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已损坏。这意味着,上述Permutation的实现可能会尝试使内存增加一倍,或者根本不使用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;
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...