【问题标题】:Issue with return value optimisation返回值优化问题
【发布时间】:2014-08-30 16:10:10
【问题描述】:

我正在阅读一些有关 C++ 中 RVO 的信息,并发现了一个奇怪的观察结果。我运行了下面的代码..

class myClass {
  private:
    int *ptr;
    static int id;

  public:
    myClass() {
      id++;
      ptr = new int[10];
      printf("Created %p id %d this %p\n", ptr, id, this);
    }

    ~myClass() {
      delete[] ptr;
      ptr = NULL;
      printf("Deleted ptr id %d this %p\n", 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 下,此代码显示没有内存泄漏,但删除无效 []。

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

【问题讨论】:

标签: c++ rvo


【解决方案1】:

这确实与返回值优化无关。只有当您使用testFunc 的结果时,RVO 才会引人注目。

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

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

【讨论】:

    【解决方案2】:

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

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

    要修复它,请按照Rule of Three 使类正确可复制(或不可复制);或者停止使用原始内存进行所有这些危险的处理,并使用std::vector 或类似方法为您正确管理数组。

    【讨论】:

      【解决方案3】:

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

      所以问题是现在var 是var1 的副本,这意味着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 %p\n", 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 %p\n", this->id, this);
          id--;
      }
      

      shared_ptr 方法的问题在于它们仍然共享相同的指针。这意味着它不像通常预期的那样表现得像副本。如果ptr 指向的值永远不会更改,则可能没问题。

      我更喜欢解决方案1。

      【讨论】:

      • 在下面的代码中,我使用了自己的复制构造函数。但我仍然可以看到一个额外的析构函数。如果我取消注释 func() 中的返回行,那么它工作正常。我对 RVO 的工作方式有点困惑..
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-02
      • 2020-06-09
      • 2016-06-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多