当通过引用传递包含指针成员的类对象时,为什么要多次调用析构函数?我该如何更正

Why is the destructor called multiple times when a class object containing a pointer member is passed by reference? How do I correct this?

本文关键字:调用 为什么 析构函数 何更正 引用 包含 指针 对象 成员      更新时间:2023-10-16

我有一个形式的类

class A
{
int **a;
//other members  
}

在某些函数中,我通过引用传递A类对象,比如obj

void func(A &o); //function declaration

func(std::ref(obj)); //function call

然而,我得到了以下错误-双重免费或损坏(!prev)

根据链接的问题,这是因为指针成员的析构函数被多次调用,这是复制时多次调用复制构造函数的结果,导致试图释放已经释放的内存。然而,当我通过引用传递完整的对象时,为什么要发生这种情况?难道不应该简单地复制整个对象的地址吗?

以上面链接和这里给出的形式实现复制构造函数的建议没有帮助,因为它们涉及在复制对象时分配新的内存量,而我希望通过引用传递对象,从而传递指针成员。

我看了这个和这个,潜在的重复,但它们并没有解决我的问题。

基于其他一些答案,我还尝试将析构函数实现为

~A()
{
delete[] a;
}

~A()
{
if(a)
{
delete[] a;
}
}

但都没有解决问题。

尝试A(A const&)=delete;A(A&&o):a(o.a){a=nullptr;}。这将删除您的复制ctor并写入一个未分配的移动ctor。

如果您试图复制类的实例,则已删除的副本ctor会给您带来编译时错误。在你的情况下,这会导致崩溃,这似乎是个好主意。

只要返回值是隐式或显式(std::move)从中移动的,那么这个move ctor将允许您从函数安全地返回类的实例。

同时考虑

A& operator=(A&&o) { 
if (this==&o) return *this; 
std::swap(a,o.a); 
delete[] o.a; 
o.a=nullptr; 
return *this; 
}
A& operator=(A const&o)=delete;

这使您可以从A移动的另一个分配给A

在编写析构函数时,应该处理移动/复制分配/构造的事实被称为五规则。

事实上,你应该避免写任何一个,这被称为零规则。

为了避免写入它们,请将a替换为unique_ptr<int[]>。现在,它为您生成移动操作和析构函数,并删除复制操作,所有操作都是自动的。

我解决了这个问题。我从一个调用复制构造函数的函数返回了该类的一个对象。我忽略了对象将按值传递的事实。

@IgorTandetnik的评论在一定程度上回答了这个问题,它证实了我观察到的问题没有发生的原因,只是我忽略了某个地方发生的传递值。

这个来自@Slava评论中提供的链接的答案对于找出问题的根源非常有用。