用C ++实现对象模型的库/方法

时间:2009-04-29 14:48:40

标签: c++ oop memory-management

如果以前曾经问过这个问题,我很抱歉,我不太确定术语或如何提问。

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

HTML的Document Object Model(DOM)就是一个例子;作为另一个(人为的)例子,假设我有这些类:

  • Entity
  • PersonEntity的子类)
  • CoupleEntity的子类)
  • Property
  • HouseProperty的子类)
  • PetProperty的子类)
  • CarProperty的子类)

和这些关系:

  • 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::mapstd::vector,但如果我这样做,我就无法存储指向这些对象的指针,因为它们可能是当地图或矢量增大或缩小时重新定位。

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

还有其他方法吗?或者是否有库使C ++中的这类内容更容易?

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

编辑2:(一旦我理顺了,我会稍微清理一下我的问题)我可以处理方法和属性名称的选择,我可以处理是否使用地图或列表或向量。这很简单。我试图解决的问题是:

  • 如果一个对象存储对另一个对象的引用,那么这些对象可能是重新分配的数据结构的一部分
  • 当对象之间存在互惠关系时,如何处理对象生命周期管理

5 个答案:

答案 0 :(得分:2)

您写过关于在std :: vector等中存储对象模型中的对象以及使用指向它们的问题。这让我想起将C ++类分为两类是很好的(我不确定这里的术语):

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

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

  3. 违反上述规则有很多很好的理由。但我观察到,从一开始就忽略它们会导致程序难以理解,不可靠(特别是在内存管理方面)并且难以维护。


    现在回到你的问题。

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

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

    同样适用于“所有内容的集合”和对象ID。您需要跟踪对象之间的关系,以避免无对象的id。它与指针有什么不同?除了得到有意义的错误而不是在整个记忆中疯狂之外,那就是; - )


    内存管理的一些想法:

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

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

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

答案 1 :(得分:1)

大约有一百万(保守估计)的方法。你真的在问“我如何用C ++设计软件”。答案是,我担心,“你的软件会做什么?” - 仅仅知道你想要处理人员和房屋是不够的。

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

杰森,我最喜欢的来源是C++ FAQ Book。问题是你有效地问“我如何使用C ++进行面向对象编程?”

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

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

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

答案 4 :(得分:0)

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

Person可能会有std::map< string, boost::shared_ptr<Person> >,因此X.getChild("George")只会在地图中查找子项并返回指针。 我认为你得到了这个概念,所以我将把剩下的作为练习留给你;)