如何使用向量通过指针引用递归结构

How to refer to recursive structs through pointers using vectors

本文关键字:引用 递归 结构 指针 何使用 向量      更新时间:2023-10-16

我有结构体,我们称它们为sn,看起来像:

struct sn {
    string name;
    vector<sn*> connected_to;
};

现在,假设我已经声明了从 0 到 9 的 connected_to 向量;并且我正在将 sn A 连接到 sn B:

A.connected_to[0] = &B; 

有一种感觉,我做这件事的方式是错误的。 本质上,我要做的是在连接结构时避免复制结构......即:

struct sn {
    string name;
    vector<sn> connected_to;
};
// ... 
A.connected_to[0] = B; 

这复制了什么吗?更根本的问题是,我当然不明白向量、指针和引用是如何真正深入工作的。

你的第二种方法可能是非法的。但是,在标准库的某些实现中,它可能会起作用。在这些情况下,您添加的对象将被复制(包括它们的所有子对象 - 复制标准容器时,也会复制它包含的所有元素(。因此,这样的数据结构只适合表示一棵树。

另一方面,您的第一种方法很好,因为指向不完整类型的指针本身就是有效类型 (§3.9.2/3 - [basic.compound](。 由于您只存储指针,因此不会复制该对象。不过,当您开始删除此图表时,您必须小心。根据要建模的图形类型,实现它们时有三种方案:

有一些限制。请注意,在您的情况下,类型仅在定义(sn(内不完整 - 在您实际使用它时,sn是完整的,因此您也可以删除它。

在一棵树的情况下,每个孩子只有一个父母。因此,在删除结构时,您将从根开始,每个节点只需删除其所有子节点。这将递归地工作到没有孩子的叶子。

为了有效地实现这一点,您可以将子项存储在boost::ptr_vector<sn>中。因此,您不必自己编写析构函数 - ptr_vector将删除其所有元素。

有向无环图 (DAG(

在 DAG 中,一个节点可以有多个父节点,因此您必须注意不要删除同一节点两次(如果每个节点只删除其所有子节点,就会发生这种情况 - 因此,ptr_vector在这里不起作用(。处理此问题的一种方法是使用引用计数 - 每个节点计算有多少其他节点指向它,并且只有当引用计数达到零时,节点才会被删除。您可以通过将节点存储在std::vector<std::shared_ptr<sn> >中(如果使用 C++11 之前的编译器,则boost::shared_ptr(来自动执行此操作。shared_ptr在内部管理引用计数,并且仅在不再有指向该对象的shared_ptr实例时(当引用计数为零时(删除它指向的对象。

循环图

在循环图中,节点也可以是它自己的父节点(如果它包含循环,则直接,或者间接地通过循环(。因此,如果每个节点删除其所有子节点,这将导致析构函数调用的无限循环。shared_ptr也可能在这里失败,因为当您有一个相互引用shared_ptr循环时,它们的引用计数永远不会达到零。现在是时候考虑拥有对象和引用对象之间的区别了。每个节点应该只有一个拥有它的父节点,但可以有多个引用它的父节点。所有者(并且只有所有者(负责删除该节点。正如我在上面链接的出色答案中所解释的那样,这可以使用shared_ptrweak_ptr的组合来实现。

A.connected_to[0] = &B;

确实复制了一些东西:表达式的临时指针值&B .

矢量模板类将始终执行自动复制构造和销毁,但基元类型的复制构造等效于基元类型(包括指针(的赋值和销毁是无操作。

指针是一种非常基本的类型 - 使用指针时几乎不会自动为您执行任何操作。在引擎盖下,它只是一个对应于内存中地址的整数值。当您取消引用指针时,编译器只是相信指针包含正确类型的对象的地址(或"指向"(该对象。

例如,给定不通过继承关联的类 Foo 和 Bar:

Foo *ptr1, *ptr2;
Bar *ptr3;
  // All pointers are uninitialized. 
  // Dereferencing them is undefined behavior. Most likely a crash.
  // The compiler will almost certainly issue a warning.
ptr1= new Foo(); // ptr1 now points to a valid Foo.
ptr2 = ptr1; // ptr2 points to the same Foo.
ptr3=(Bar*)ptr1; // This is an obvious programmer error which I am making here for demonstration.
 // ptr3 points to the same block of memory as ptr1 & 2.
 // Dereferencing it is likely to do strange things.
delete ptr1; // The compiler is allowed to set ptr1 to 0, but you can't rely on it.
  // In either case dereferencing ptr1 is once again undefined behavior
  // and the value of ptr2 is unchanged.

与初始化之前相比,如果编译器在删除后看到ptr1取消引用,则发出警告的可能性要小得多。如果您在通过 ptr1 删除对象后取消引用ptr2,它几乎永远不会发出警告。如果你不像别人警告你的那样小心,你的指针向量可能会导致你无意中以这种方式调用未定义的行为。

我向Bar*引入了极其错误的Foo*转换,以说明编译器对你的绝对信任。编译器允许您这样做,并且当您取消引用 ptr3 时,它们会很乐意将这些位视为 Bar 。

C++ 标准库提供了很少的模板类,这些模板类提供类似指针的行为,具有更高的自动安全性。例如,std::shared_pointer

std::shared_ptr是管理对象生存期的智能指针, 通常分配new .多个shared_ptr对象可以管理 同一对象;当最后一个剩余时,对象被销毁 指向它的shared_ptr被销毁或重置。对象是 使用删除表达式或提供的自定义删除程序销毁 在施工期间shared_ptr

如果您的环境尚未提供 c++11 标准库,则它可能提供 boost 库或 std::tr1:: 命名空间。两者都提供了非常相似的shared_ptrstd::auto_ptr ,您肯定拥有,是相似的,但只允许一个auto_ptr在给定时间引用对象。(C++11 引入了std::unique_ptr作为auto_ptr的预期替代品。该auto_ptr与大多数 std 模板容器不兼容。 unique_ptr可以放置在带有 std::move 的模板容器中。

可以通过

保留或获取指针并使用它来破坏这些类中的任何一个,例如

Foo *basic_ptr=new Foo();
std::auto_ptr<Foo> fancy_ptr(basic_ptr);
delete basic_ptr; // Oops! This statement broke our auto_ptr.

如果您在自动存储中传入变量的地址,您也会破坏它们:

Foo aFoo;
std::auto_ptr<Foo> fancy_ptr(&aFoo); // automatic storage automatically breaks auto_ptr

如果你只是做std::shared_ptr<sn> fresh_sn(new sn())然后使用std::vector< std::shared_ptr<sn> >你会没事的。