问题描述
最初,我遇到这样的问题:我有一个带有数据的向量,并且想执行一次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}
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;
}