7

只是我一直遇到的一个概念性问题。在我目前的项目中,感觉就像我过度使用了 boostsmart_ptrptr_container库。我boost::ptr_vectors 在许多不同的对象中创建并调用 transfer() 方法将某些指针从一个移动boost::ptr_vector到另一个。

据我了解,清楚地显示堆分配对象的所有权很重要。

我的问题是,是否希望使用这些 boost 库来创建属于对象的堆分配成员,然后get()在进行任何处理时使用指向这些成员的普通指针。

例如... 一个游戏可能有一组属于它的 Tiles。在boost::ptr_vector. 游戏结束后,这些图块应自动释放。

但是,如果我想暂时将这些 Tiles 放入 Bag 对象中,我应该boost::ptr_vector在包中创建另一个并将游戏的 Tiles 转移到 Bag 中,transfer()还是应该创建一个std::vector<Tile*>Tiles* 引用游戏中的 Tiles 并传递它到袋子里?

谢谢。

**编辑我应该指出,在我的示例中,The Game 将有一个 Bag 对象作为成员。袋子只会装满游戏拥有的瓷砖。因此,如果没有游戏,袋子就不会存在。

4

8 回答 8

16

您应该只在所有权明确转移的情况下使用拥有智能指针和指针容器。对象是否是临时的并不重要——重要的是它是否拥有所有权(因此,前所有者是否放弃所有权)。

如果您创建一个临时指针向量只是为了将其传递给其他函数,而原始向量ptr_vector仍然引用所有这些对象,则没有所有权转移,因此您应该使用 plainvector作为临时对象 - 就像您使用 raw将单个对象传递给ptr_vector接受指针但不将其存储在任何地方的函数的指针。

于 2009-09-03T17:35:10.240 回答
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。:-(

于 2009-09-03T17:52:14.863 回答
5

最有可能的是,您正在寻找的解决方案是

std::vector<Tile>

大多数时候不需要指针。向量已经负责所包含对象的内存管理。瓷砖归游戏所有,不是吗?明确这一点的方法是将对象本身放在游戏类中——您通常需要指针和动态分配单个对象的唯一情况是您需要 1)多态性或 2)共享所有权。

但是指针应该是例外,而不是规则。

于 2009-09-03T18:38:58.213 回答
1

boost::ptr_vector仅用于改进使用指针向量的语义。如果在您的示例中,原始数组可能在使用临时向量集时被破坏,那么您绝对应该使用shared_ptrs 的向量来防止它们在仍在使用时被删除。如果不是,那么一个普通的旧指针向量可能是合适的。选择std::vector与否boost::ptr_vector并不重要,除了使用向量的代码看起来有多漂亮。

于 2009-09-03T17:36:34.493 回答
1

在您的游戏图块示例中,我的第一直觉是创建一个vector<Tile>,而不是 avector<Tile*>或 ptr_vector。然后随意使用指向图块的指针,而不用担心所有权,只要持有指针的对象不会超过图块矢量。你说过它只有在游戏结束时才会被摧毁,所以这应该不难。

当然,这可能是有原因的,例如,因为 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.
}
于 2009-09-03T17:56:37.520 回答
1

如果游戏拥有棋子,则游戏将负责删除。

听起来袋子从未真正拥有这些对象,因此它不应该负责删除它们。因此我会在 Game 对象中使用 ptr_vector。但是在包中使用 std::vector 。

注意:我永远不会让任何使用包的人从包中检索指向图块的指针。他们应该只能从包中检索对瓷砖的引用。

于 2009-09-03T20:00:16.723 回答
0

如果将瓷砖放在袋子中并且有人偷了袋子,您将失去所有瓷砖。所以Tiles虽然是暂时的,但在短时间内属于Bag。我想你应该在这里转让所有权。

但更好的意见是不要混淆所有权,因为我不明白为什么在这种特殊情况下需要它。如果幕后有什么,继续,做出你的选择。

于 2009-09-03T18:01:35.293 回答
0

我同意使用向量而不是直接跳入堆分配的 T 是一种很好的本能,但我可以很容易地看到 Tile 是一种类型,而复制构造很昂贵,这可能会使向量的增长策略变得不切实际。当然,这可能最好用一个列表来解决,而不是一个向量......

于 2009-09-09T18:52:11.197 回答