【问题标题】:Best Practice For List of Polymorphic Objects in C++C++ 中多态对象列表的最佳实践
【发布时间】:2009-07-03 18:44:47
【问题描述】:

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

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

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

上面的代码肯定缺少一些项目,首先,基类将有一个需要类型的构造函数,派生类将在构造过程中内部调用它。此外,在 GenericShape 类中,将存在复制/赋值构造函数/操作符。

抱歉,这篇文章很长,试图充分解释我的意图。关于这一点,并重申:以上是我的解决方案,但这可能有一些严重的缺陷,我很高兴听到它们以及其他解决方案!

谢谢

【问题讨论】:

    标签: c++ polymorphism


    【解决方案1】:

    std::list(或 std::list)有什么问题?

    这将是实现具有多态行为的shapes 列表的惯用方式。

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

    用户不应垂头丧气,而应使用提供的多态性和基本(形状)操作。考虑一下他们为什么会对向下转型感兴趣,如果您找到这样做的理由,请返回绘图板并重新设计,以便您的基础提供所有需要的操作。

    然后如果用户想要向下转换,他们应该使用dynamic_cast,并且他们将获得您试图在包装器中提供的相同行为(如果向下转换指针,则为空指针或 std::bad_cast 异常以供参考向下转型)。

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

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

    如果不处理反序列化(我稍后会回来),与在列表中存储(智能)指针相比,您的解决方案需要重新访问适配器以为添加到层次结构中的每个其他类添加新代码.

    现在是反序列化问题。

    建议的解决方案是使用普通的 std::list,一旦您构建了列表,就可以立即执行绘图和序列化:

    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 来减少代码膨胀,而不是手动迭代。问题是你不能虚拟化 构造,因为在构造对象之前你不知道它实际上是什么类型。在解决了对已知层次结构中的一个元素进行反序列化的问题后,反序列化列表就很简单了。

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

    我假设您已经为所有形状定义了唯一的 shape type 值,并且您的序列化从打印出该 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
       };
    }
    

    如果将反序列化函数转换为抽象工厂,在创建新类时,类本身会注册它的反序列化方法,则可以进一步减轻对反序列化函数的工作需求。

    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
    }
    

    在现实生活中,我会使用 boost::function 而不是函数指针,这样可以使代码更简洁明了,但会为示例代码添加另一个依赖项。此解决方案要求在初始化期间(或至少在尝试反序列化之前)所有类在 shape_deserializator 对象中注册它们各自的方法。

    【讨论】:

      【解决方案2】:

      您可以通过使用模板(用于构造函数和转换器)来避免 GenericShape 中的大量重复,但缺少的关键位是让它继承自 Shape 并实现它的虚拟——没有它它是不可用的,这是信封/实现习语的一个非常正常的变体。

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

      【讨论】:

        【解决方案3】:

        我会在 STL 容器中建议 boost::shared_pointer&lt;Shape&gt;。然后使用dynamic_cast 向下转换保证类型正确性。如果你想提供辅助函数来抛出异常而不是返回NULL,然后按照Alex's suggestion 定义一个模板辅助函数,如:

        template <typename T, typename U>
        T* downcast_to(U *inPtr) {
            T* outPtr = dynamic_cast<T*>(inPtr);
            if (outPtr == NULL) {
                throw std::bad_cast("inappropriate cast");
            }
            return outPtr;
        }    
        

        并像这样使用它:

        void some_function(Shape *shp) {
            Circle *circ = downcast_to<Circle>(shp);
            // ...
        }
        

        使用像GenericShape 这样的单独类与从Shape 继承的每个类的耦合太强了。我想知道这是否会被视为代码气味...

        【讨论】:

          【解决方案4】:

          我希望使用这个系统 安全的;我不希望用户拥有 他/她时出现未定义的错误 错误地转换基类指针 到错误的派生类型。

          为什么会出现未定义的错误? dynamic_cast 的行为是完美定义的,如果您将基类指针强制转换为错误的派生类型,则会捕获错误。这真的像是在重新发明轮子。

          另外,我想要 可能的工作 复制/序列化这个列表是 自动照顾。这 原因是,作为一个新派生的 类型已添加我不想 搜索许多源文件和 确保一切都会 兼容。

          我不确定这里有什么问题。如果所有派生类都是可序列化和可复制的,那还不够好吗?你还需要什么?

          我也不确定前两个要求是什么。 你是什​​么意思,ABC应该“执行一个共同的功能”?如果派生类的作用只是执行相同的通用功能、可复制和可序列化,那么派生类的意义何在?

          那为什么不让一个非抽象类可序列化和可复制呢?

          我可能在这里遗漏了一些重要的东西,但我不认为你已经解释了你想要实现的目标。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-06-16
            • 2019-03-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-02-04
            相关资源
            最近更新 更多