抱歉,如果之前有人问过这个问题,我不太确定术语或如何提出问题。

我想知道是否有用于在 C++ 中实现对象模型的库或最佳实践。如果我有一组类,其中这些类的实例可以相互关联,并且可以通过各种方法相互访问,那么我想选择一组好的底层数据结构来管理这些实例及其相互关系。这在 Java 中很容易,因为它为我处理内存分配和垃圾收集,但在 C++ 中我必须自己做。

HTML 的 文档对象模型 (DOM) 就是一个例子;作为另一个(人为的)例子,假设我有这些课程:

  • Entity
  • Person (子类 Entity)
  • Couple (子类 Entity)
  • Property
  • House (子类 Property)
  • Pet (子类 Property)
  • Car (子类 Property)

以及这些关系:

  • Entity
    • 有 1 home 班级的 House
    • 有 0 个或多个 pets 班级的 Pet
    • 有 0 个或多个 cars 班级的 Car
    • 有 0 个或多个 children 班级的 Person
  • Person
    • 有 0 或 1 spouse 班级的 Person
    • 有 0 或 1 marriage 班级的 Couple
    • 有 0 或 1 parents 班级的 Entity (在这个模型中,如果父母不存在,他们就不存在!)
  • Couple
    • 有 2 members 班级的 Person
  • Property
    • 有 1 owner 班级的 Entity

现在我已经考虑了这些对象及其关系,我想开始制作数据结构、方法和字段来处理它们,这就是我迷失的地方,因为我必须处理内存分配和生命周期管理以及所有这些东西。您可能会遇到如下问题:我可能想将一个对象放入 std::map 或一个 std::vector, ,但如果我这样做,我将无法存储指向这些对象的指针,因为当地图或向量增大或缩小时它们可以重新定位。

当我经常使用 COM 时,我使用的一种方法是拥有一个包含所有内容的隐藏集合。集合中的每个对象都有一个唯一的 ID(数字或名称),可以通过该 ID 从集合中查找对象,并且每个对象都有一个指向集合的指针。这样,如果您有一个对象想要指向另一个对象,我不会存储指向另一个对象的指针,而是存储 ID 并可以通过隐藏集合查找它。我可以使用引用计数来自动处理生命周期问题(除了不相交循环的情况,有时这不是问题)。

还有其他方法吗?或者是否有库可以使 C++ 中的此类操作变得更容易?

编辑: 然后你还有其他问题,比如对象之间的关系在很多情况下可能是可变的,你必须提前考虑应该如何存储对象的引用,以及应该提供哪些方法来相互访问对象。例如,如果我有一个句柄 Person X,我想表示“找到 X 的孩子叫 George”的概念,那么我必须存储名字“George”而不是孩子的编号:孩子们可能存储在向量中,我也许能够调用 X.getChildCount() 和 X.getChild(0),但“George”可能并不总是孩子号 0,因为其他孩子可能会插入到“George”之前” 在子向量中。或者 X 可能有两个、三个或四个其他孩子也叫“乔治”。或者“乔治”可能会将他的名字改为“安东尼”或“乔治娜”。在所有这些情况下,最好使用某种唯一的不可变 ID。

编辑2: (一旦我弄清楚了这个问题,我就会稍微清理一下我的问题)我可以处理方法和属性名称的选择,我可以处理是否使用映射、列表或向量。这相当简单。我要具体解决的问题是:

  • 当一个对象可能是重新分配的数据结构的一部分时,如何让一个对象存储对另一个对象的引用
  • 当对象之间存在相互关系时,如何处理对象生命周期管理
有帮助吗?

解决方案

您写了有关将对象模型中的对象存储在 std::vector 等中的文章。以及使用指向它们的指针的问题。这提醒我,最好将 C++ 类分为两类(我不确定这里的术语):

  1. 实体类 它们代表属于对象模型一部分的对象。它们通常是多态的,或者将来可能会是多态的。它们是在堆上创建的,并且始终由指针或智能指针引用。您永远不会将它们作为类/结构成员直接在堆栈上创建,也不会直接将它们放入像 std::vectors 这样的容器中。它们没有复制构造函数,也没有运算符=(您可以使用某些 Clone 方法创建新副本)。如果这对您有意义,您可以比较它们(它们的状态),但它们不可互换,因为它们具有同一性。每两个对象都是不同的。

  2. 价值类别 它实现原始的用户定义类型(如字符串、复数、大数、智能指针、句柄包装器等)。它们直接在堆栈上创建或作为类/结构成员创建。它们可以通过复制构造函数和运算符=进行复制。它们不是多态的(多态性和operator=不能很好地协同工作)。您经常将它们的副本放入 stl 容器中。您很少将指向它们的指针存储在独立的位置。它们是可以互换的。当两个实例具有相同的值时,您可以将它们视为相同。(尽管包含它们的变量是不同的东西。)

有很多很好的理由可以打破上述规则。但我观察到,从一开始就忽略它们会导致程序不可读、不可靠(尤其是在内存管理方面)并且难以维护。


现在回到你的问题。

如果您想存储一个具有复杂关系的数据模型,并且可以轻松地执行诸如“查找 X 的名为 George 的孩子”之类的查询,为什么不考虑一些内存中关系数据库呢?

请注意,当您要有效地实现a)更复杂的双向关系和b)基于不同对象属性的查询时,您可能需要创建与关系数据库内部功能非常相似的索引数据结构。您的实施(在单个项目中会有很多实施)真的会更加有效和稳健吗?

“所有内容的集合”和对象 ID 也是如此。您需要跟踪对象之间的关系,以避免 id 没有对象。它与指针有什么不同?其他然后得到有意义的错误而不是在内存中疯狂,那就是;-)


内存管理的一些想法:

  • 强大的所有权:当您可以声明某个实体仅在其所有者存在时存在,并且不可能存在独立存在的指向它的指针时,您可以在所有者的析构函数中(或使用scoped_ptr)删除它。

  • 有人已经提出了 smart_ptr。它们非常棒,可以与 stl 容器一起使用。不过,它们是基于参考计数器的,因此不要创建循环:-(。我不知道有任何广泛使用的 C++ 自动指针可以处理循环。

  • 也许有一些顶级对象拥有所有其他对象。例如。通常,您可以说所有部分都属于文档、算法或交易。它们可以在顶级对象的上下文中创建,然后在删除其顶级对象时(当您从内存中删除文档或完成算法执行时)自动删除它们。当然,您不能在顶级对象之间共享片段。

其他提示

大约有一百万(保守估计)的方法可以实现这一点。您实际上是在问“我如何用 C++ 设计软件”。恐怕答案是:“您的软件将做什么?” - 仅仅知道您想与人与房屋打交道是不够的。

这不是 OOP 的全部意义吗?您所要求的是一个实现细节,您隐藏在这些类的公共接口后面,因此不必担心,因为您可以在不更改接口的情况下更改它?因此,继续,按照您建议的方式尝试一下。然后,如果出现性能、内存或其他问题,您可以修复实现,而不会破坏其余代码。

在我看来,将数据存储在数据库中并使用某种对象关系映射可能是另一种值得考虑的选择。

您可以使用 boost::shared_ptr 来解决内存问题。然后,您可以自由复制shared_ptr,从函数返回它,将其用作局部变量等。

A Person 然后可以有一个 std::map< string, boost::shared_ptr<Person> >, , 所以 X.getChild("George") 只需在地图中查找子项并返回指针。我想您已经明白了这个概念,所以我将剩下的留给您作为练习;)

杰森,我最喜欢的来源是 C++ 常见问题解答书. 。问题是您实际上是在问“我如何使用 C++ 进行面向对象编程?”

我能在 SO 答案中说的最好的话是:

所有这些东西都将成为 C++ 中的类,并且关系等看起来很像您习惯的垃圾收集语言:如果您需要一个人与其名为“george”的孩子之间的关系,您可以选择一个可以存储按名称索引的人或孩子的数据结构。

如果遵循一些规则,内存管理实际上比直接 C 更容易:确保所有需要它们的对象都有析构函数,确保析构函数清理对象拥有的所有内容,然后确保始终将这些动态构造的对象放在不再需要时超出范围的上下文中。这些并不能涵盖所有情况,但它们可以帮助您避免大约 80% 的内存分配错误。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top