这是一个我一直在遇到的概念性问题。在我当前的项目中,感觉我过度使用了boost smart_ptr
和ptr_container
库。我在创建boost::ptr_vectors
在许多不同的对象中调用transfer()方法将某些指针从一个boost::ptr_vector
移动到另一个{。
据我了解,清楚地显示堆分配对象的所有权非常重要。
我的问题是,是否需要使用这些boost库来创建属于对象的堆分配成员,然后在进行任何处理时通过get()
使用指向这些成员的常规指针。
例如......
游戏可能有一组属于它的Tiles。在boost::ptr_vector
中创建这些图块可能是有意义的。当游戏结束时,应自动释放这些图块。
但是如果我想暂时将这些Tiles放在Bag对象中,我是否应该在包中创建另一个boost::ptr_vector
并通过transfer()
将游戏的Tiles转移到Bag中
我应该创建一个std::vector<Tile*>
,其中Tiles *引用Tiles
在游戏中并将其传递给Bag?
感谢。
**编辑 我应该指出,在我的例子中,Game会有一个Bag对象作为成员。袋子只会填充游戏所拥有的瓷砖。所以如果没有游戏,行李将不存在。
答案 0 :(得分:15)
您应该只使用拥有智能指针和指针容器,其中有明确的传输所有权。对象是否是临时的并不重要 - 重要的是它是否拥有所有权(因此,前任所有者是否放弃所有权)。
如果创建指针的临时向量只是为了将其传递给其他函数,并且原始ptr_vector
仍然引用所有这些对象,则没有所有权转移,因此您应该使用纯vector
对于临时对象 - 正如您使用原始指针将单个对象从ptr_vector
传递给接受指针的函数,但不会将其存储在任何位置。
答案 1 :(得分:6)
根据我的经验,出现了三种主要的所有权模式。我将它们称为树,DAG和图形。
最常见的是一棵树。父母拥有其子女,而子女又拥有子女等等。你通常在这里看到auto_ptr,scoped_ptr,裸指针和boost ptr_x类。在我看来,一般应避免使用裸指针,因为它们根本不传达所有权语义。
第二个最常见的是DAG。这意味着您可以拥有共享所有权。父母拥有的子女也可能是父母拥有的其他子女的子女。 TR1和boost shared_ptr模板是这里的主要角色。没有周期时,参考计数是一种可行的策略。
第三个最常见的是完整的图表。这意味着您可以拥有周期。有一些策略可以破坏这些周期并以一些可能的错误来源为代价返回DAG。这些通常由TR1或boost的weak_ptr模板表示。
使用weak_ptr无法分解为DAG的完整图形是一个在C ++中无法轻易解决的问题。唯一好的处理程序是垃圾收集方案。它们也是最通用的,能够很好地处理其他两个方案。但是他们的普遍性是有代价的。
在我看来,你不能过度使用ptr_x容器类或auto_ptr,除非你真的应该使用对象容器而不是指针容器。 shared_ptr可能被滥用。仔细考虑您是否真的需要DAG。
当然我认为人们应该只使用scope_ptrs的容器而不是boost ptr_x类,但那将不得不等待C ++ 0x。 : - (
答案 2 :(得分:5)
最有可能的是,您正在寻找的解决方案是
std::vector<Tile>
大部分时间都没有需要指针。该向量已经处理了所包含对象的内存管理。瓷砖归游戏所有,不是吗?明确的方法是将对象本身放在游戏类中 - 通常需要指针并动态分配单个对象的唯一情况是,如果您需要1)多态,或2)共享所有权。
但是指针应该是例外,而不是规则。
答案 3 :(得分:1)
boost::ptr_vector
仅用于改进使用指针向量的语义。如果在您的示例中,在使用临时矢量集时可能会销毁原始数组,那么您绝对应该使用shared_ptr
的向量来防止它们在仍在使用时被删除。如果没有,那么一个普通的旧指针矢量可能是合适的。无论您选择std::vector
还是boost::ptr_vector
都没关系,除了使用该向量的代码看起来有多好。
答案 4 :(得分:1)
在您的游戏磁贴示例中,我的第一直觉是创建vector<Tile>
,而不是vector<Tile*>
或ptr_vector。然后根据需要使用指向tile的指针,而不必担心所有权,前提是持有指针的对象不会超过tile的向量。你说过它只会在游戏结束时被销毁,所以这应该不会很难。
当然可能存在这是不可能的原因,例如因为Tile是一个多态基类,并且tile本身并不是所有相同的类。但这是C ++,而不是Java,并不是每个问题都需要动态多态。但即使你真的需要指针,你仍然可以复制那些没有任何所有权语义的指针,只要指向的对象的范围和存储持续时间被理解为比指针的使用范围和持续时间更宽:
int main() {
vector<Tile*> v;
// fill in v, perhaps with heap objects, perhaps with stack objects.
runGame(v);
}
void runGame(const vector<Tile*> &v) {
Tile *firsttile = v[0];
vector<Tile*> eventiles;
eventiles.push_back(v[2]);
eventiles.push_back(v[4]);
// and so on. No need to worry about ownership,
// just as long as I don't keep any pointers beyond return.
// It's my caller's problem to track and free heap objects, if any.
}
答案 5 :(得分:1)
如果游戏拥有牌,则游戏可以响应牌局。
听起来这个包从来没有真正拥有这些物品所以它不应该被删除它们。因此我会在Game对象中使用ptr_vector。但是在包里使用std :: vector。
注意:我永远不会让任何人使用这个包取回指向包里的瓷砖的指针。他们应该只能从包中找回瓷砖的参考。
答案 6 :(得分:0)
如果将瓷砖放入袋中并且有人偷走了袋子,则会丢失所有瓷砖。因此Tiles,虽然临时,但他们属于到袋子很短的时间。我想你应该在这里转让所有权。
但更好的意见不是弄乱所有权,因为我不明白为什么在这种特殊情况下需要它。如果场景背后有什么东西,请继续,做出选择。
答案 7 :(得分:0)
我同意使用向量而不是直接跳入堆分配的T是一种很好的直觉,但我可以很容易地看到Tile是一种类型,而复制构造很昂贵,这可能会妨碍向量的增长策略的实用性。当然,最好用列表解决,而不是矢量...