2014-08-30 59 views
0

我读了一些关于RVO的C++,并发现了一个奇怪的观察。我跑到下面的代码..问题与返回值优化

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这个代码显示没有内存泄漏,但无效删除[]。

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

+1

规则3(5)...复制构造函数... – Jarod42 2014-08-30 16:12:31

+0

可能重复[什么是三条规则?](http://stackoverflow.com/questions/4172722/what-is-the-rule -of-three) – 2014-08-30 16:16:39

回答

1

这实际上与返回值优化没有任何关系。只有在使用testFunc的结果时,RVO才会显着。

你所看到的问题是,因为你的类是只使用默认的拷贝构造函数,所以当var传递给testFuncptr成员被复制,就像一个普通的指针,而无需创建它是对象的新副本指向。

正因为如此,你最终这都指向同一个底层int阵列中两个myClass对象,这两个对象的析构函数被调用时,他们试图删除同一int阵列的两倍。

0

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

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

要解决此问题,请按照Rule of Three的说明使该类可以正确复制(或不可复制);或者停止使用原始内存的所有这些危险的操作,并使用std::vector或类似命令为您正确管理阵列。

0

当调用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 %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指向的值永远不会更改,那么可以确定。

我宁愿解决方案。

+0

在下面的代码中,我使用了自己的拷贝构造函数。但我仍然可以看到额外的析构函数。如果我取消注释func()中的返回行,那么它工作正常。我有点困惑与RVO的工作方式.. – 2014-09-18 16:18:13