返回值优化问题

Issue with return value optimisation

本文关键字:问题 优化 返回值      更新时间:2023-10-16

我在c++中读了一些关于RVO的文章,发现了一个奇怪的观察结果。我运行了以下代码。。

class myClass {
  private:
    int *ptr;
    static int id;
  public:
    myClass() {
      id++;
      ptr = new int[10];
      printf("Created %p id %d this %pn", ptr, id, this);
    }
    ~myClass() {
      delete[] ptr;
      ptr = NULL;
      printf("Deleted ptr id %d this %pn", this->id, this);
      id--;
    }
};
int myClass::id = 0;
myClass testFunc(myClass var) {
  myClass temp;
  return temp;
}
int main() {
  myClass var1, var2;
  testFunc(var1);
  return 0;
}

我得到的o/p是

Created 0x9b14008 id 1 this 0xbfe3e910
Created 0x9b14038 id 2 this 0xbfe3e914
Created 0x9b14068 id 3 this 0xbfe3e91c
Deleted ptr id 3 this 0xbfe3e91c
Deleted ptr id 2 this 0xbfe3e918
Deleted ptr id 1 this 0xbfe3e914
Deleted ptr id 0 this 0xbfe3e910

testFunc调用中的临时复制变量实际上会引起一些问题。它删除var1的ptr成员,这可以通过调用指针0xbfe3e918中的析构函数来看到。在valgring下,此代码显示没有mem泄漏,但删除[]无效。

我有点困惑额外的析构函数是如何被调用的,为什么没有相应的构造函数来调用呢??

这实际上与返回值优化无关。只有当您使用testFunc的结果时,RVO才会明显。

您看到的问题是,您的类只是使用默认的复制构造函数,因此当var传递给testFunc时,ptr成员将被复制为常规指针,而不会创建它所指向对象的新副本。

因此,您最终会得到两个指向同一个底层int数组的myClass对象,当调用这两个对象的析构函数时,它们会两次尝试删除同一个int数组。

按值传递var1作为函数参数会创建一个副本。这是通过(隐式定义的)复制构造函数完成的,这就是为什么你的程序不打印任何东西——你只打印默认构造函数中的东西。

现在您遇到了一个大问题:对象的两个副本都包含指向同一数组的指针,并且在销毁时都会尝试删除它。这种双重删除是一个错误,会导致未定义的行为。

要修复它,要么遵循"三条规则"使类正确地可复制(要么不可复制);或者停止所有这些危险的原始内存,使用std::vector或类似的方法为您正确管理数组。

当调用testFunc时,var被初始化为来自main的var1的副本。这是使用默认的复制构造函数完成的,因此"缺少"构造函数调用。这只是调用了另一个构造函数。

所以问题是,现在varvar1的副本,这意味着var.ptr必须具有与var1.ptr相同的值。

1.解决方案是提供自己的复制构造函数来处理这种情况。一个解决方案是进行深度复制:

myClass(const myClass& o) {
    id++;
    ptr = new int[10];
    memcpy(o.ptr, ptr, 10);
    printf("Created copy %p of %p id %d this %pn", ptr, o.ptr, id, this);
}

2。另一种解决方案是使用shared_ptr,它可以跟踪具有特定指针副本的对象的数量:

//int *ptr;           //Replace this
shared_ptr<int> ptr;  //With this
//Initialize ptr:
myClass():ptr(new int[10]) {
    //...
}
// Also eliminate cleenup. It's handled by shared_ptr:
~myClass() {
    printf("Deleted ptr id %d this %pn", this->id, this);
    id--;
}

shared_ptr方法的问题在于它们仍然共享相同的指针。这意味着它的行为不像通常预期的那样是一个副本。如果ptr指向的值从未更改,则可以。

我更喜欢1的解决方案。