存储基类指针列表的常见做法是什么,每个基类指针都可以描述多态派生类?
为了详细阐述并为了一个简单的例子,假设我有一组具有以下目标的类:
现在除了这个必需的功能外,我想解决以下要点:
下面的代码演示了一个简单的例子,并且我提出了(我再次寻找一个经常深思熟虑的方法,这样做,我的可能不太好)解决方案。
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类中,将出现复制/赋值构造函数/运算符。
很抱歉这篇长篇文章,试图完全解释我的意图。在那个注释,并重新迭代:上面是我的解决方案,但这可能有一些严重的缺陷,我很乐意听到他们,以及那里的其他解决方案!
谢谢
答案 0 :(得分:5)
std :: list&lt;的问题是什么?形状*&gt; (或者std :: list&lt; boost :: shared_ptr&gt;)?
这将是实现具有多态行为的shape
列表的惯用方法。
- 我希望使用这个系统是安全的;当他/她错误地将基类指针强制转换为错误的派生类型时,我不希望用户有未定义的错误。
醇>
用户不应该向下转换,而应使用提供的多态性和基本(形状)操作。考虑为什么他们会对向下转发感兴趣,如果你找到理由这样做,请回到绘图板并重新设计,以便你的基地提供所有需要的操作。
然后,如果用户想要向下转换,则应使用dynamic_cast
,并且它们将获得您尝试在包装器中提供的相同行为(如果向下转换指针或std :: bad_cast异常,则为空指针参考向下转发)。
您的解决方案增加了一个间接级别,并且(使用提供的界面)要求用户在使用前尝试猜测形状类型。您为每个派生类提供了两个转换运算符,但用户必须在尝试使用这些方法之前调用它们(不再是多态的)。
- 此外,我希望尽可能多地复制/序列化此列表的工作,以便自动处理。原因是,当添加新的派生类型时,我不想搜索许多源文件并确保所有内容都兼容。
醇>
在不处理反序列化的情况下(我将在稍后回来),与在列表中存储(智能)指针相比,您的解决方案需要重新访问适配器,以便为添加到层次结构的每个其他类添加新代码
现在反序列化问题。
建议的解决方案是使用普通的std :: list&lt; boost :: shared_ptr&gt;,一旦你建立了列表,就可以直接执行绘图和序列化:
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来减少代码膨胀而不是手动迭代。问题是你不能像构建对象那样虚拟化构造,你不知道它实际上是什么类型。在解决了已知层次结构的一个元素的反序列化问题后,对列表进行反序列化是微不足道的。
此问题的解决方案从未像上面的代码一样简洁明了。
我将假设您已为所有形状定义了唯一的形状类型值,并且您通过打印出该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&lt;&gt;而不是函数指针,使代码更清晰,更清晰,但添加另一个依赖于示例代码。此解决方案要求在初始化期间(或至少在尝试反序列化之前)所有类在 shape_deserializator 对象中注册其各自的方法。
答案 1 :(得分:3)
你可以通过使用模板(对于构造函数和转换器)来避免GenericShape
中的大量重复,但缺少的关键位是让它继承自Shape
并实现其虚拟化 - 没有它它是无法使用的,它是信封/实现习语的一个非常正常的变种。
您可能希望使用auto_ptr
(或更智能的指针),而不是使用Shape的裸指针; - )。
答案 2 :(得分:0)
我会在STL容器中提出boost::shared_pointer<Shape>
。然后使用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
的每个类过于强烈耦合。我想知道这是否会被认为是代码嗅觉 ...
答案 3 :(得分:0)
我希望使用这个系统 安全;我不希望用户拥有 他/她时未定义的错误 错误地转换基类指针 错误的派生类型。
为什么会出现未定义的错误? dynamic_cast
的行为完全定义良好,如果将基类指针强制转换为错误的派生类型,则会捕获错误。这真的好像重新发明轮子。
此外,我想要多少 可能的工作 复制/序列化此列表 照顾自动。该 原因是,作为一个新的派生 添加类型我不想要 搜索许多源文件和 确保一切都会如此 兼容。
我不确定这里的问题是什么。如果所有派生类都是可序列化和可复制的,那还不够好吗?你还需要什么?
我也不确定如何制作前两个要求。 你是什么意思,ABC应该“强制执行一个共同的功能”?如果派生类只是为了执行相同的通用功能,可以复制和序列化,那么有什么意义呢?
为什么不将一个非抽象类序列化并可复制呢?
我可能在这里遗漏了一些重要的东西,但我并不认为你已经解释了你想要实现的目标。