C ++设计相关问题

时间:2010-04-25 13:45:30

标签: c++ polymorphism

这是问题的情节:假设我有一些对象的抽象类,我们称之为Object。它的定义包括2D位置和尺寸。让它也有一些用于渲染的virtual void Render(Backend& backend) const = 0方法。

现在我专门化我的继承树并添加RectangleEllipse类。猜猜他们没有自己的属性,但他们会有自己的virtual void Render方法。假设我实现了这些方法,因此Render Rectangle实际上绘制了一些矩形,而椭圆也是如此。

现在,我添加了一个名为Plane的对象,该对象定义为class Plane : public Rectangle,其私有成员为std::vector<Object*> plane_objects;

之后我添加了一个方法来向我的飞机添加一些物体。

这就是问题所在。 如果我将此方法设计为void AddObject(Object& object),我会遇到麻烦,因为我无法调用虚函数,因为我必须做plane_objects.push_back(new Object(object));之类的事情,对于矩形应该是push_back(new Rectangle(object)),对于圆圈应该是new Circle(...)

如果我将此方法实现为void AddObject(Object* object),它看起来不错,但是在其他地方这意味着调用plane.AddObject(new Rectangle(params));,这通常是一团糟,因为那时我不清楚我的程序的哪一部分应该释放分配的内存。

[“在销毁飞机时?为什么?我们确定拨打AddObject的电话只是AddObject(new something}。”

我想使用第二种方法引起的问题可以使用智能指针解决,但我确信必须有更好的东西。

任何想法?

5 个答案:

答案 0 :(得分:3)

使用智能指针,例如boost::shared_ptrboost::intrusive_ptr

为什么你认为有比使用智能指针更好的东西?如果您不采取特殊措施来确保代码中的异常安全,那么将原始指针存储在容器中通常会让您大吃一惊。

答案 1 :(得分:3)

您的实际问题似乎是管理对象的生命周期。想到的四种可能性是:

  1. 您的容器(即Plane)将承担所有包含对象的所有权,因此delete一旦它自身被销毁就会成为其所有权。

  2. 您的容器(Plane)不承担所有权,任何向容器添加对象的人都将负责销毁它们。

  3. 自动管理对象的生命周期。

  4. 您可以通过为容器提供实际对象的克隆来避免此问题。容器管理对象的副本,调用者管理原始对象。

  5. 你现在拥有的似乎是#4的方法。通过做:

    plane_objects.push_back(new Object(object));
    

    将对象的副本插入容器中。因此问题就消失了。问问自己这是否真的是你想要的,或者上述选择之一是否更合适。


    选项#1和#2易于实现,因为它们定义了您的实现必须遵循的合同。选项#3将要求例如智能指针,或其他涉及引用计数的解决方案。

    如果您想继续遵循选项#4 的方法,您可以例如使用Object方法扩展clone类,以便返回正确类型的对象。这将摆脱不正确的new Object(...)

    class Object
    {
        public:
            virtual Object* clone() const = 0;
            ...
    };
    
    ...
    
    Object* Rectangle::clone() const
    {
        return new Rectangle(*this);  // e.g. use copy c'tor to return a clone
    }
    

    P.S。:请注意STL容器似乎如何处理此问题:假设您声明vector<Foo>。此向量将包含插入其中的对象的副本(我的答案中的选项#4)。但是,如果您将集合声明为vector<Foo*>,它将包含对原始对象的引用,但它不会管理它们的生命周期(我的答案中的选项#2)。

答案 2 :(得分:1)

您可以在界面中添加clone方法,以克隆类型层次结构的实例。每个具体实现都会生成该特定类的新实例,因此保留所有类型信息。

class Object {
public:
  virtual Object clone() = 0;
};

class Rectangle : public Object {
public:
  virtual Rectangle clone() { /* create and return an identical Rectangle */ }
};

class Ellipse : public Object {
public:
  virtual Ellipse clone() { /* create and return an identical Ellipse */ }
};

class Plane : public Rectangle {
  std::vector<Object*> plane_objects;
public:
  virtual Plane clone() { /* create and return an identical Plane */ }
  void AddObject(const Object& object) {
    plane_objects.push_back(object.clone());
  }
};

这意味着该平面是其所有对象的所有者,因此它也应该在其析构函数中销毁它们。如果这些对象只在内部访问,它们可以用普通指针存储 - 尽管智能指针使得处理更简单,更安全。但是,如果它们被发布,最好使用智能指针来存储它们。

答案 3 :(得分:1)

  

摧毁飞机?为什么?是我们吗   确保调用AddObject   仅作为AddObject(新的东西)完成。

无法确定。但某些东西必须管理对象的生命周期。你应该清楚地记录那是什么东西。

答案 4 :(得分:1)

首先,您需要弄清楚您的对象标识和所有权语义:

每个对象是真实身份还是简单地引用真实实体?对于前者,您不能使用clone(),并且您需要使用引用计数(通过boost :: shared_ptr)或通过引用传递,如果实例的所有者保证比对它的引用更长。对于后者,clone()可能更有意义,您可以安全地忽略所有权问题。

一般来说,你应该避免传递裸指针并使用boost :: shared_ptr代替或引用。