C++:删除被两个指针引用的对象

4

我想知道在C++中是否有一种优雅的方法来解决以下问题:

我有一个模拟器应用程序,其中包含通过通道连接的多个组件。通道可以是网络通道(需要两个应用程序实例)或虚拟本地通道。有两个接口:IChannelInIChannelOut以及两个对应的变量:

IChannelIn* in;
IChannelOut* out;

DummyChannel 是一个同时实现了 IChannelInIChannelOut 接口的类。它只是将输入复制到输出。此外,还有 TCPChannelIn: public IChannelIn 和单独的 TCPChannelOut: public IChannelOut

现在,根据用户的选择,我要么创建一个 DummyChannel

DummyChannel* d = new DummyChannel;
in = d;
out = d;

或者是两个单独的对象:in = new TCPChannelIn; out = new TcpChannelOut 问题是:析构函数应该做什么?
~App::App()
{
    delete in;
    delete out;
}

出现错误的原因是delete in;删除了虚拟通道d,导致delete out删除了已经被删除的东西。

有没有一种优雅的解决方法呢?

7个回答

9
您需要类似以下内容的东西:
~App::App()
{
    if (in != out)
    {
        delete out;
    }
    delete in;
}

当通道不同时,指针也将不同。

然而,正如danatel在评论中指出的那样,inout是不可比较的。一个安全(但不太优雅)的解决方案是使用一个dummy标志。如果inout被设置为虚拟通道,则析构函数将变为:

~App::App()
{
    if (!dummy)
    {
        delete out;
    }
    delete in;
}

虽然我对此并不满意。

目前我能看到的唯一解决方案是更改 IChannelInIChannelOut 的定义,以便可以安全比较它们。


1
不能直接比较输入和输出,它们是不同类型的(IChannelIn 和 IChannelOut)。即使它们的地址也不同,因为在 in = d 之后,in 指向 d 的子对象。 - danatel
从您发布的代码中,我不明白为什么in和out都被设置为d(新的DummyChannel)。还有其他的因素吗? - ChrisF
DummyChannel是公共的IChannelIn和公共的IChannelOut。但是IChannelIn和IChannelOut是不相关的类。DummyChannel不是IChannelIn的父类,相反,IChannelIn是DummyChannel的父类。 - danatel
啊,我明白了。现在不确定该建议什么 - 我猜你不能改变你的设计。 - ChrisF

7

如果你正在使用boost,可以考虑使用boost::shared_ptr。这将在最后一个实例被释放时自动删除对象。


我在这个项目中没有使用boost。我将来会考虑使用boost,但这次,增加另一个依赖项并不值得。 - danatel
在这种情况下,请使用std::tr1::shared_ptr。它不是boost(好吧,在某些实现中它是boost::shared_ptr,但无论如何它都是即将到来的标准的一部分)。 - David Rodríguez - dribeas
我正在使用Visual Studio 2005。不确定它是否编译tr1。 - danatel
std::auto_ptr没有引用计数。因此,如果您为同一对象创建两个auto_ptr实例,则它们都将在其d'tor中调用delete,从而回到双重删除。boost :: shared_ptr是引用计数的。在内部隐藏着一个引用计数,该计数将设置为2。当第一个引用消失时(例如,在),计数将减少到1。当第二个引用消失时(例如,在外部),计数将减少到0并删除对象。 - Stephen Nutt

3
if (in == out) {
    delete in;
}
else {
    delete in;
    delete out;
}

?


请参考ChrisF的解决方案中的评论。代码无法编译。 - MSalters

3
经验之谈:在使用delete时,应该与new相匹配。
~App::App()
{
    delete out;
    // the next line tries to free
    // memory no longer in the app's
    // control invoking UB
    delete in;
}

在这里,你会遇到经典的“双重释放”问题。
~App::App()
{
    if (in != out)
    {
        delete out;
    }
    // if in == out the out object is not deleted
    // for a non trivial object this tantamounts to
    // leaking resources held by TCPChannelOut other
    // than d
    delete in;
}

唯一的解决方案似乎是使用智能引用计数指针。

2
那根本没有解决他的问题。在删除in后将其设置为NULL并不能使delete out行更有效。 - Pesto
这个答案有什么问题吗?当你有多个这样的共享指针而不仅仅是两个时,这种方法才有效。 - dirkgently
即使您在删除变量"in"并将其设置为NULL后,"out"仍然指向旧对象。 - danatel
基本问题是指针不共享。两个不同的指针'in'和'out'存储相同的地址。清零一个指针将无法防止存储地址的重复删除。 - Abhay
是的,我更新了我的帖子,指出为什么这个检查也不起作用。 - dirkgently

3

你的类是如何“知道”这是一个自由存储区指针的呢?例如,以下代码有什么问题吗?

DummyChannel d;
in = &d;
out = &d;

这是一段非常合理的代码,但是当您尝试删除其中任何一个指针时,您的析构函数将会崩溃。

简而言之:回收资源是最初分配资源的人的工作。如果您的类传递了一个指针,那么该类无法知道也不关心释放。这完全由调用者负责。

解决这个问题的优雅方法是使用智能指针,如前所述,而无需让客户端做大量工作。


谢谢你的回答,它让我找到了正确的方向。必须以与分配相同的方式回收资源。因此,我将保留模拟器分配的所有通道的单独列表,并从列表中销毁对象。in和out只是非拥有指针。 - danatel

0

我总是在设计时思考对象所有权:

  • 谁创建了这个对象?
  • 谁拥有(因此删除了该对象)?
  • 如何避免使用已经销毁的对象的引用(或者如何确定所引用的对象是否仍然有效)?

当尝试回答这些问题时,我也确定了实现的方式(析构函数,智能指针等)。


0
不要忘记将IChannelIn和IChannelOut的析构函数设置为虚函数。如果析构函数不是虚函数,则在调用IChannel析构函数(即删除IChannel指针内容时)时,TCPChannel或DummyChannel的析构函数将不会被调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接