C++中多态对象列表的最佳实践

5 c++ polymorphism

存储基类指针列表的常见做法是什么,每个基类指针都可以描述多态派生类?

为了详细说明并且为了一个简单的例子,我假设我有一组具有以下目标的类:

  1. 一个抽象基类,其目的是在其派生类上强制执行公共功能.
  2. 一组派生类:可以执行通用功能,本质上是可复制的(这很重要),并且是可序列化的.

现在除了这个必需的功能,我想解决以下要点:

  1. 我希望使用这个系统是安全的; 当他/她错误地将基类指针强制转换为错误的派生类型时,我不希望用户有未定义的错误.
  2. 此外,我希望尽可能多地复制/序列化此列表的工作,以便自动处理.原因是,当添加新的派生类型时,我不想搜索许多源文件并确保所有内容都兼容.

下面的代码演示了一个简单的例子,我提出了(我再次寻找一个经常深思熟虑的方法,这样做,我的可能不是那么好)解决方案.

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类中,将存在复制/赋值构造函数/运算符.

对不起,很长的帖子,试图完全解释我的意图.在那个注释,并重新迭代:上面是我的解决方案,但这可能有一些严重的缺陷,我很乐意听到他们,以及其他解决方案!

谢谢

Dav*_*eas 5

std :: list <shape*>(或者std :: list <boost :: shared_ptr>)有什么问题?

这将是实现shape具有多态行为的s 列表的惯用方法.

  1. 我希望使用这个系统是安全的; 当他/她错误地将基类指针强制转换为错误的派生类型时,我不希望用户有未定义的错误.

用户不应该低估,而应使用多态和提供的基本(形状)操作.考虑为什么他们会对向下转发感兴趣,如果你找到理由这样做,请回到绘图板并重新设计,以便你的基地提供所有需要的操作.

然后,如果用户想要垂头丧气,他们应该使用dynamic_cast,他们会得到你想在你的包装提供相同的行为(无论是空指针向下转换指针或一个std ::参考向下转换bad_cast除外).

您的解决方案增加了间接级别(使用提供的界面)要求用户在使用前尝试猜测形状类型.您为每个派生类提供了两个转换运算符,但用户必须在尝试使用这些方法之前调用它们(不再是多态的).

  1. 此外,我希望尽可能多地复制/序列化此列表的工作,以便自动处理.原因是,当添加新的派生类型时,我不想搜索许多源文件并确保所有内容都兼容.

如果没有处理反序列化(我会在稍后回来),您的解决方案相比,在列表存储(智能)指针,需要重新审视适配器为每被添加到层级所有其他类中添加新的代码.

现在反序列化问题.

建议的解决方案是使用普通的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对象中注册其各自的方法.


Ale*_*lli 3

您可以通过使用模板(对于构造函数和转换器)来避免大量重复GenericShape,但缺少的关键点是让它继承Shape并实现其虚拟对象 - 没有它它就无法使用,有了它它就是信封/上的一个非常正常的变体实施习语。

您可能也想使用auto_ptr(或更智能的指针)而不是指向 Shape 的裸指针;-)。