C ++中多态对象列表的最佳实践 [英] Best Practice For List of Polymorphic Objects in C++

查看:126
本文介绍了C ++中多态对象列表的最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

What is a common practice for the storage of a list of base class pointers each of which can describe a polymorphic derived class?

一个简单的例子假设我有一组具有以下目标的类:

To elaborate and in the interest of a simple example lets assume that I have a set of classes with the following goals:


  1. 一个抽象基类,

  2. 一组派生类,它们可以执行常见的功能,本质上是可复制的(这很重要),并且是可序列化的。

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

Now alongside this required functionality I want to address the following key points:


  1. 我想使用这个系统是安全的;我不希望用户在错误地将基类指针转换为错误的派生类型时出现未定义的错误。

  2. 此外,我希望尽可能多的复制/序列化此列表以自动处理。原因是,作为一个新的派生类型添加,我不想搜索许多源文件,并确保一切都将兼容。

下面的代码演示了一个简单的例子,我的建议(再次我正在寻找一个常见的方法,这样做,我可能不是那么好)解决方案。

The following code demonstrates a simple case of this, and my proposed (again I am looking for a common well thought out method of doing this, mine may not be so good) solution.

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.
};

上面的代码肯定缺少一些项目,首先基类将有一个构造函数, ,派生类在其构造期间内部调用它。此外,在GenericShape类中,将存在复制/赋值构造函数/运算符。

The above code is certainly missing some items, firstly the base class would have a single constructor requiring the type, the derived classes would internally call this during their construction. Additionally in the GenericShape class, copy/assignment constructor/operator would be present.

对于长篇文章,试图完全解释我的意图。在这方面,并重申:上面是我的解决方案,但这可能有一些严重的缺陷,我很高兴听到他们,和其他解决方案!

Sorry for the long post, trying to explain my intents fully. On that note, and to re-iterate: above is my solution, but this likely has some serious flaws and I would be happy to hear about them, and the other solutions out there!

谢谢

推荐答案

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

What is the problem of a std::list< shape* > (or a std::list< boost::shared_ptr > thereof)?

这将是实现 shape 具有多态行为。

That would be the idiomatic way of implementing a list of shapes with polymorphic behavior.



  1. 我想使用此系统安全;当用户错误地将基类指针转换为错误的派生类型时,我不希望用户出现未定义的错误。


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

Users should not downcast, but rather use the polymorphism and the base (shape) operations provided. Consider why they would be interested in downcasting, if you find a reason to do so, go back to drawing board and redesign so that your base provides all needed operations.

然后如果用户想要downcast,他们应该使用 dynamic_cast ,他们将获得你想在包装器中提供的相同的行为(如果向下转换指针的空指针或std :: bad_cast exception for reference downcasting)。

Then if the user wants to downcast, they should use dynamic_cast, and they will get the same behavior you are trying to provide in your wrapper (either a null pointer if downcasting pointers or a std::bad_cast exception for reference downcasting).

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

Your solution adds a level of indirection and (with the provided interface) require the user to try guessing the type of shape before use. You offer two conversion operators to each of the derived classes, but the user must call them before trying to use the methods (that are no longer polymorphic).



  1. 此外,我希望尽可能多地复制/序列化此列表以自动处理。原因是,作为一个新的派生类型添加,我不想搜索许多源文件,并确保一切都将兼容。


如果不处理反序列化(我将稍后回来),与在列表中存储(智能)指针相比,您的解决方案需要重新访问适配器以添加新代码

Without dealing with deserialization (I will come back later), your solution, as compared to storing (smart) pointers in the list, requires revisiting the adapter to add new code for each and every other class that is added to the hierarchy.

现在是反序列化问题。

提出的解决方案使用一个plain std :: list< boost :: shared_ptr>,一旦你创建了列表,绘图和序列化可以立即执行:

The proposed solution is using a plain std::list< boost::shared_ptr >, once you have the list built, drawing and serialization can be performed right out of the box:

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 ) );
}

我使用boost :: bind来减少代码膨胀,而不是手动迭代。问题是,你不能像在对象构建之前那样虚拟化构造,你不能知道它实际上是什么类型。在解序列化已知层次结构中的一个元素的问题之后,反序列化该列表是微不足道的。

Where I have used boost::bind to reduce code bloat instead of iterating manually. The problem is that you cannot virtualize construction as before the object has been constructed you cannot know what type it actually is. After the problem of deserializing one element of a known hierarchy is solved, deserializing the list is trivial.

解决这个问题从来不像上面的代码那么干净和简单。

Solutions to this problem are never as clean and simple as the code above.

我将假设您为所有形状定义了唯一的形状类型值,并且您的序列化通过打印出该id开始。也就是说,序列化的第一个元素是类型id。

I will assume that you have defined unique shape type values for all shapes, and that your serialization starts by printing out that id. That is, the first element of serialization is the type 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
   };
}

您可以进一步减轻对解串器函数的工作进入一个抽象工厂,在创建一个新类时,类本身注册它的反序列化方法。

You can further alleviate the need to work on the deserializer function if you convert it into an abstract factory where upon creation of a new class the class itself registers it's deserialization method.

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
}



在现实生活中, :function<>而不是函数指针,使代码更清晰,更清晰,但添加了另一个依赖的示例代码。该解决方案要求在初始化期间(或至少在尝试反序列化之前)所有类在* shape_deserializator *对象中注册它们各自的方法。

In real life, I would have used boost::function<> instead of the function pointers, making the code cleaner and clearer, but adding yet another dependency to the example code. This solution requires that during initialization (or at least before trying to deserialize) all classes register their respective methods in the *shape_deserializator* object.

这篇关于C ++中多态对象列表的最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆