进入智能指针,如何处理代表所有权

Getting into smart pointers, how to deal with representing ownership?

本文关键字:处理 所有权 何处理 智能 指针      更新时间:2023-10-16

我做了一个动态图结构,其中节点和弧都是类(我的意思是弧是内存中的实际实例,它们不是节点到节点的邻接列表所暗示的)。 每个节点都有一个指向其连接到的弧的指针列表。 每个弧都有 2 个指向它连接的 2 个节点的指针。

删除节点将对其每个弧调用删除。 每个弧删除都会从其连接的 2 个节点中的弧列表中删除其指针。 简化:

~node()
{
while(arcs_list.size())
{
delete arcs_list[arcs_list.size()-1];
}
}
~arc()
{
node_from.remove_arc(this);
node_to.remove_arc(this);
}

如果我想在这里开始使用智能指针,我该如何继续? 每个弧拥有 2 个节点,还是 2 个节点共享单个弧的所有权? 我在考虑一个shared_ptr,但共享指针只会在两个节点都被删除时删除弧。如果我只删除一个节点,如果我使用 shared_ptr,我仍然必须显式删除它的所有弧。这完全违背了首先不使用原始指针的观点。

节点可以单独存在;每个弧由两个节点拥有,并且只有在这两个节点存在时才能存在。

我应该使用其他类型的智能指针来处理这个问题吗? 还是原始指针只是简单的方法?

每个弧拥有 2 个节点,还是 2 个节点共享单个弧的所有权?

你自己回答了这个问题:

节点

可以单独存在;每个弧由两个节点拥有,并且只有在这两个节点都存在时才能存在。

当对象 A 拥有对象 B 时,对象 A 可以在销毁 B 后存在,但销毁 A 意味着销毁 B.应用于您的情况,两个节点共享弧的所有权。

我应该使用其他类型的智能指针来处理这个问题吗?还是原始指针只是简单的方法?

啊,是的。这才是真正的问题。对于这种情况,没有预制的智能指针。但是,我不会在节点和/或 arc 类中使用原始指针。这意味着这些类需要在其主要目的之上实现内存管理。(最好让每个班级做好一件事,然后尝试做很多事情并失败。我看到了一些可行的选择。

1:编写自己的智能指针

编写一个可以封装必要的销毁逻辑的类。节点和/或弧类将使用新类而不是标准智能指针(而不是原始指针)。花一些时间确保您的设计决策是可靠的。我猜你的新类需要某种函数/可调用来告诉它如何从它所在的列表中删除自己。或者可以将一些数据(如指向节点的指针)从 arc 类转移到新类。

我还没有弄清楚细节,但这将是一个合理的方法,因为这种情况不适合任何标准的智能指针。关键点是不要将此逻辑直接放在节点和 arc 类中。

2:标记无效弧

如果您的程序可以忍受不立即释放内存,则可以采用不同的方法来解决弧形删除问题。无需立即从其节点列表中删除弧,只需将弧标记为不再有效即可。当一个节点需要访问它的弧时,它(或者更好的是,它的列表)会检查它访问的每个弧——如果弧无效,它可以在那时从列表中删除。从两个列表中删除节点后,正常的shared_ptr功能将启动以删除 arc 对象。

节点迭代其弧的频率越低,这种方法的实用性就越低。因此,需要做出判断。

如何标记弧无效?天真的方法是给它一个布尔标志。将标志设置为在构造函数中false,并true何时应将弧视为已删除。有效,但确实需要一个新的领域。这可以在不使弧类膨胀的情况下完成吗?嗯,大概每个弧都需要指向其节点的指针。由于 arc 不拥有其节点,因此这些可能是弱指针。因此,定义弧无效的一种方法是检查弱指针是否expired()。(请注意,当直接删除 arc 时,可以手动reset()弱指针,而不是通过节点的删除。因此,过期的弱指针不一定意味着关联的节点消失,只是弧不再指向它。

在 arc 类很大的情况下,您可能希望立即丢弃其大部分内存,只留下一个存根。您可以添加间接寻址级别来实现此目的。从本质上讲,节点将共享一个指向唯一指针的指针,而唯一指针将指向您当前称为 arc 类的内容。删除圆弧时,唯一指针reset(),释放大部分圆弧的内存。当此唯一指针为空时,弧无效。(看起来Davis Herring的答案是另一种以更少的内存开销获得这种效果的方法,如果你可以接受一个对象存储一个shared_ptr给自己的话。

3:使用Boost.Bimap

如果你可以使用Boost,他们有一个看起来可以解决你的问题的容器:Boost.Bimap。但是,你问,我不是已经使用邻接列表打折了吗?是的,但这个 Bimap 不仅仅是一种将节点相互关联的方式。此容器支持具有与每个关系关联的其他信息。也就是说,Bimap 中的每个关系都将表示一个弧,并且它将具有与弧的信息相关联的对象。似乎很适合你的情况,你可以让别人担心内存管理(总是一件好事,只要你能相信某人的能力)。

由于节点可以单独存在,因此它们由图形(可能是也可能不是单个对象)拥有,而不是弧(即使作为共享所有权)。 正如您所观察到的,弧节点对弧的所有权与通常的shared_ptr情况是双重的,即任何一个所有者都足以使对象保持活动状态。 尽管如此,您仍然可以在此处使用shared_ptrweak_ptr(以及指向节点的原始非拥有指针):

struct Node;
struct Arc {
Node *a,*b;
private:
std::shared_ptr<Arc> skyhook{this};
public:
void free() {skyhook.reset();}
};
struct Node {
std::vector<std::weak_ptr<Arc>> arcs;
~Node() {
for(const auto &w : arcs)
if(const auto a=w.lock()) a->free();
}
};

显然,其他Node操作必须检查空的弱点指针,并可能定期清理它们。

请注意,异常安全(包括构造shared_ptr时的vs.bad_alloc)在构造Arc时需要更加小心。