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

时间:2011-12-23 09:26:37

标签: c++ pointers vector struct

我有结构,让我们称之为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; 

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

2 个答案:

答案 0 :(得分:11)

您的第二种方法是probably illegal。但是,在标准库的某些实现中,它可能有效。在这些情况下,您添加的对象将被复制(包括其所有子对象) - 复制标准容器时,它所包含的所有元素也会被复制。因此,这样的数据结构仅适用于表示树。

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

<子> There are some restrictions。请注意,在您的情况下,类型仅在定义内部(sn)不完整 - 在您实际使用它时,sn已完成,因此您也可以将其删除。

Trees

如果是树,每个孩子只有一个父母。因此,在删除结构时,您将从根目录开始,每个节点只需删除其所有子节点。这将递归地处理没有子节点的叶子。

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

Directed Acyclic Graphs (DAG)

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

Cyclic Graphs

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

答案 1 :(得分:2)

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取消引用而不是初始化之前,则发出警告的可能性要小得多。如果在通过ptr2删除对象后取消引用ptr1,它几​​乎不会发出警告。如果你不小心别人警告过你,你的指针向量可能会导致你以这种方式无意中调用未定义的行为。

我将Foo*极其错误的强制转换为Bar*,以说明编译器对您的绝对信任。编译器允许您执行此操作,并且当您取消引用ptr3时,将很乐意将这些位视为条形。

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

  

std::shared_ptr是一个管理对象生命周期的智能指针,   通常分配new。可以管理多个shared_ptr个对象   同一个对象;最后剩下的对象被销毁了   指向它的shared_ptr被破坏或重置。对象是   使用delete-expression或提供的自定义删除器销毁   在施工期间到shared_ptr

如果您的环境尚未提供c ++ 11标准库,则它可能提供boost库或std::tr1::命名空间。两者都提供了非常相似的shared_ptr。您确定拥有的std::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> >,那就没事了。