这是问题的情节:假设我有一些对象的抽象类,我们称之为Object
。它的定义包括2D位置和尺寸。让它也有一些用于渲染的virtual void Render(Backend& backend) const = 0
方法。
现在我专门化我的继承树并添加Rectangle
和Ellipse
类。猜猜他们没有自己的属性,但他们会有自己的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
}。”
我想使用第二种方法引起的问题可以使用智能指针解决,但我确信必须有更好的东西。
任何想法?
答案 0 :(得分:3)
使用智能指针,例如boost::shared_ptr
或boost::intrusive_ptr
。
为什么你认为有比使用智能指针更好的东西?如果您不采取特殊措施来确保代码中的异常安全,那么将原始指针存储在容器中通常会让您大吃一惊。
答案 1 :(得分:3)
您的实际问题似乎是管理对象的生命周期。想到的四种可能性是:
您的容器(即Plane
)将承担所有包含对象的所有权,因此delete
一旦它自身被销毁就会成为其所有权。
您的容器(Plane
)不承担所有权,任何向容器添加对象的人都将负责销毁它们。
自动管理对象的生命周期。
您可以通过为容器提供实际对象的克隆来避免此问题。容器管理对象的副本,调用者管理原始对象。
你现在拥有的似乎是#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代替或引用。