我正在读一些关于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下这段代码显示没有内存泄漏,但无效删除[]。
我有点困惑如何调用额外的析构函数以及为什么没有相应的构造函数调用相同的?
答案 0 :(得分:1)
这与返回值优化没有任何关系。只有在使用testFunc
的结果时才会注意到RVO。
您看到的问题是因为您的类只使用了默认的复制构造函数,因此当var
传递给testFunc
时,ptr
成员会像常规指针一样被复制,而不是创建它指向的对象的新副本。
因此,你最终会得到两个指向同一个底层myClass
数组的int
个对象,并且当调用这两个对象的析构函数时,它们会尝试删除相同的int
数组。 {1}}数组两次。
答案 1 :(得分:0)
按值传递var1
作为函数参数会创建一个副本。这是通过(隐式定义的)复制构造函数完成的,这就是为什么你的程序不打印任何东西 - 你只在默认构造函数中打印一些东西。
您现在遇到了一个大问题:对象的两个副本都包含指向同一个数组的指针,并且两者都会在销毁时尝试删除它。这种双重删除是一个错误,导致未定义的行为。
要修复它,请按照Rule of Three使该类正确可复制(或不可复制);或者使用原始内存停止所有这些危险的捣乱,并使用std::vector
或类似方法为您正确管理数组。
答案 2 :(得分:0)
当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 。