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

时间:2019-04-18 17:53:05

标签: c++ pointers smart-pointers raw-pointer

我制作了一个动态图结构,其中节点和弧都是类(我的意思是弧是内存中的实际实例,节点到节点的邻接表并不暗示它们)。 每个节点都有一个指向其连接的弧的指针列表。 每个弧都有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 个答案:

答案 0 :(得分:2)

  

每个弧是否拥有2个节点,或者2个节点共享一个弧的所有权?

您自己回答了这个问题:

  

节点可以单独存在;每个弧都由两个节点所有,只有这两个节点都存在,它才能存在。

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

  

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

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

1:编写自己的智能指针

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

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

2:标记无效弧

如果程序不能立即释放内存,则可以采用其他方法来解决弧删除。无需立即从其节点列表中删除弧,只需将弧标记为不再有效即可。当节点需要访问其弧时,它(或更好的是,其列表)将检查其访问的每个弧–如果弧无效,则可以在那时将其从列表中删除。将节点从两个列表中删除后,常规的shared_ptr功能将开始删除弧对象。

这种方法的有用性在于,节点在其弧上进行迭代的频率降低。因此,需要做出判断。

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

在arc类相当大的情况下,您可能希望立即丢弃其大部分内存,仅留下一个存根。您可以添加一个间接级别来完成此操作。本质上,节点将共享一个指向唯一指针的指针,并且唯一指针将指向您当前称为的弧类。弧删除后,唯一指针为reset(),从而释放了弧的大部分内存。当此唯一指针为null时,弧无效。 (如果您可以接受一个自己存储shared_ptr的对象,那么看来Davis Herring的答案是另一种以较少的内存开销获得这种效果的方法。)

3:使用Boost.Bimap

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

答案 1 :(得分:1)

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

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

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