我有结构,让我们称之为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;
这复制了吗?更基本的问题当然是我不明白向量,指针和引用是如何真正深入工作的。
答案 0 :(得分:11)
您的第二种方法是probably illegal。但是,在标准库的某些实现中,它可能有效。在这些情况下,您添加的对象将被复制(包括其所有子对象) - 复制标准容器时,它所包含的所有元素也会被复制。因此,这样的数据结构仅适用于表示树。
另一方面,你的第一种方法很好,因为指向不完整类型的指针本身就是一种有效类型(§3.9.2/ 3 - [basic.compound])✝。由于您只存储指针,因此不会复制该对象。当您开始删除此图表时,您必须小心。根据您正在建模的图形类型,实现它们时有三种情况:
✝ <子> There are some restrictions。请注意,在您的情况下,类型仅在定义内部(sn
)不完整 - 在您实际使用它时,sn
已完成,因此您也可以将其删除。 子>
如果是树,每个孩子只有一个父母。因此,在删除结构时,您将从根目录开始,每个节点只需删除其所有子节点。这将递归地处理没有子节点的叶子。
要有效地实现此目的,您可以将孩子存储在boost::ptr_vector<sn>
中。因此,您不必自己编写析构函数 - ptr_vector
将删除其所有元素。
在DAG中,一个节点可以有多个父节点,因此你必须注意不要删除相同的节点两次(如果每个节点只删除它的所有子节点,就会发生这种情况 - 因此,ptr_vector
会不在这里工作)。处理此问题的一种方法是使用引用计数 - 每个节点计算有多少其他节点指向它,并且只有当引用计数达到零时,才会实际删除该节点。您可以通过将节点存储在std::vector<std::shared_ptr<sn> >
(或boost::shared_ptr
,如果您使用预C ++ 11编译器)来自动执行此操作。 shared_ptr
在内部管理引用计数,并且只会在没有更多shared_ptr
- 实例指向该对象时(当引用计数为零时)删除它指向的对象。
在循环图中,节点也可以是它自己的父节点(如果它包含循环,则直接或通过循环间接地)。因此,如果每个节点删除其所有子节点,那将导致无限循环的析构函数调用。此处shared_ptr
也可能失败,因为当您拥有cycle of shared_ptr
referencing each other时,其引用计数将永远不会达到零。现在是时候考虑拥有对象和引用之间的区别。每个节点应该只拥有一个拥有它的父级,但可以有几个引用它。所有者和唯一所有者负责删除该节点。正如我在上面链接的优秀答案中所解释的,这可以使用shared_ptr
和weak_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> >
,那就没事了。