存储基类指针列表的常见做法是什么,每个基类指针都可以描述多态派生类?
为了详细说明并且为了一个简单的例子,我假设我有一组具有以下目标的类:
现在除了这个必需的功能,我想解决以下要点:
下面的代码演示了一个简单的例子,我提出了(我再次寻找一个经常深思熟虑的方法,这样做,我的可能不是那么好)解决方案.
class Shape {
public:
virtual void draw() const = 0;
virtual void serialize();
protected:
int shapeType;
};
class Square : public Shape
{
public:
void draw const; // draw code here.
void serialize(); // serialization here.
private:
// square member variables.
};
class Circle : public Shape
{
public:
void draw const; // draw code here.
void serialize(); // serialization here.
private:
// circle member variables.
};
// The proposed solution: rather than store list<shape*>, store a generic shape type which
// takes care of copying, saving, loading and throws errors when erroneous casting is done.
class GenericShape
{
public:
GenericShape( const Square& shape );
GenericShape( const Circle& shape );
~GenericShape();
operator const Square& (); // Throw error here if a circle tries to get a square!
operator const Circle& (); // Throw error here if a square tries to get a circle!
private:
Shape* copyShape( const Shape* otherShape );
Shape* m_pShape; // The internally stored pointer to a base type.
};
Run Code Online (Sandbox Code Playgroud)
上面的代码肯定缺少一些项目,首先基类会有一个需要类型的构造函数,派生类会在构造过程中在内部调用它.此外,在GenericShape类中,将存在复制/赋值构造函数/运算符.
对不起,很长的帖子,试图完全解释我的意图.在那个注释,并重新迭代:上面是我的解决方案,但这可能有一些严重的缺陷,我很乐意听到他们,以及其他解决方案!
谢谢
std :: list <shape*>(或者std :: list <boost :: shared_ptr>)有什么问题?
这将是实现shape具有多态行为的s 列表的惯用方法.
- 我希望使用这个系统是安全的; 当他/她错误地将基类指针强制转换为错误的派生类型时,我不希望用户有未定义的错误.
用户不应该低估,而应使用多态和提供的基本(形状)操作.考虑为什么他们会对向下转发感兴趣,如果你找到理由这样做,请回到绘图板并重新设计,以便你的基地提供所有需要的操作.
然后,如果用户想要垂头丧气,他们应该使用dynamic_cast,他们会得到你想在你的包装提供相同的行为(无论是空指针向下转换指针或一个std ::参考向下转换bad_cast除外).
您的解决方案增加了间接级别(使用提供的界面)要求用户在使用前尝试猜测形状类型.您为每个派生类提供了两个转换运算符,但用户必须在尝试使用这些方法之前调用它们(不再是多态的).
- 此外,我希望尽可能多地复制/序列化此列表的工作,以便自动处理.原因是,当添加新的派生类型时,我不想搜索许多源文件并确保所有内容都兼容.
如果没有处理反序列化(我会在稍后回来),您的解决方案相比,在列表存储(智能)指针,需要重新审视适配器为每被添加到层级所有其他类中添加新的代码.
现在反序列化问题.
建议的解决方案是使用普通的std :: list <boost :: shared_ptr>,一旦你建立了列表,就可以直接执行绘图和序列化:
class shape
{
public:
virtual void draw() = 0;
virtual void serialize( std::ostream& s ) = 0;
};
typedef std::list< boost::shared_ptr<shape> > shape_list;
void drawall( shape_list const & l )
{
std::for_each( l.begin(), l.end(), boost::bind( &shape::draw, _1 ));
}
void serialize( std::ostream& s, shape_list const & l )
{
std::for_each( l.begin(), l.end(), boost::bind( &shape::serialize, _1, s ) );
}
Run Code Online (Sandbox Code Playgroud)
我使用boost :: bind来减少代码膨胀而不是手动迭代.问题是你不能像构建对象之前那样虚拟化构造,你不知道它实际上是什么类型.在解决了已知层次结构的一个元素的反序列化问题之后,对列表进行反序列化是微不足道的.
这个问题的解决方案永远不会像上面的代码那样干净简单.
我将假设您已为所有形状定义了唯一的形状类型值,并且您的序列化通过打印出该ID开始.也就是说,序列化的第一个元素是类型id.
const int CIRCLE = ...;
class circle : public shape
{
// ...
public:
static circle* deserialize( std::istream & );
};
shape* shape_deserialize( std::istream & input )
{
int type;
input >> type;
switch ( type ) {
case CIRCLE:
return circle::deserialize( input );
break;
//...
default:
// manage error: unrecognized type
};
}
Run Code Online (Sandbox Code Playgroud)
如果将其转换为抽象工厂,则可以进一步减少处理反序列化函数的需要,在创建新类时,类本身会注册它的反序列化方法.
typedef shape* (*deserialization_method)( std::istream& );
typedef std::map< int, deserialization_method > deserializer_map;
class shape_deserializator
{
public:
void register_deserializator( int shape_type, deserialization_method method );
shape* deserialize( std::istream& );
private:
deserializer_map deserializers_;
};
shape* shape_deserializator::deserialize( std::istream & input )
{
int shape_type;
input >> shape_type;
deserializer_map::const_iterator s = deserializers_.find( shape_type );
if ( s == deserializers_.end() ) {
// input error: don't know how to deserialize the class
}
return *(s->second)( input ); // call the deserializer method
}
Run Code Online (Sandbox Code Playgroud)
在现实生活中,我会使用boost :: function <>而不是函数指针,使代码更清晰,更清晰,但在示例代码中添加了另一个依赖项.此解决方案要求在初始化期间(或至少在尝试反序列化之前),所有类在shape_deserializator对象中注册其各自的方法.
您可以通过使用模板(对于构造函数和转换器)来避免大量重复GenericShape,但缺少的关键点是让它继承Shape并实现其虚拟对象 - 没有它它就无法使用,有了它它就是信封/上的一个非常正常的变体实施习语。
您可能也想使用auto_ptr(或更智能的指针)而不是指向 Shape 的裸指针;-)。