【问题标题】:C++ Downcasting to Derived Class based off VariableC++ 向下转换为基于变量的派生类
【发布时间】:2011-06-24 17:40:35
【问题描述】:

假设我有一个基类“Shape”,以及派生类“Triangle”、“Square”和“Circle”。 “Shape”的成员是一个 int “shapeType”。

如果 shapeType==1,那么它是一个三角形。 如果 shapeType==2,那么它是一个正方形。 如果 shapeType==3,则为圆形。

我有兴趣知道我只有一个曾经是派生对象的“Shape”对象,如果有办法通过使用 shapeType 值“动态”向下转换为正确的派生类。

我知道我可以做一个硬编码的 switch 语句,大致如下:

Triangle* t;
Square* s;
Circle* c;

switch (shape->shapeType) {
case 1:
   t = (Triangle*)shape;
case 2: 
   ...
}

然而,以上要求我为每一个派生类的可能性做一个指针。我想知道是否有一种方法可以在不对每个类进行硬编码的情况下做到这一点,而是以某种方式确定类类型映射,其中键是 shapeType,值是类类型。

【问题讨论】:

  • 真正的问题是您为什么要这样做。您当然可以使用 RTTI(参见 <typeinfo>)来学习动态类型,但在实践中这绝不是必需的。您应该为Shape 提供所需的虚拟接口功能并使用它们。
  • 你打算如何处理向下转换的指针?要么你要对它们做一些适用于许多形状的事情,在这种情况下它应该是一个虚函数,或者你要根据形状做一些不同的事情,无论如何你都必须硬编码。

标签: c++ inheritance casting


【解决方案1】:

如果他们有虚函数,那么使用dynamic_cast:

t = dynamic_cast<Triangle*>(shape);
if ( t )
{
     //use t
}

但请注意:您应该尝试以几乎不需要使用dynamic_cast 的方式定义类和虚函数。一般来说,更喜欢定义明确的接口和多态性。

这是一个例子,

class Shape
{
   public:
     virtual ~Shape() {} //destructor must be virtual - important!
     virtual double Area() const = 0;
};

class Triangle : public Shape
{
   public:
     Triangle(double a, double b, double c);
     virtual double Area() const 
     {
         //calculate area and return it!
     }
};

Shape *s = new Triangle(10, 20, 30);
double aread = s->Area(); //calls Triangle::Area()

无需使用shapeType 变量。

【讨论】:

    【解决方案2】:

    dynamic_cast 是您问题的答案。

    说明

    它用于从基类向下转换为派生类,同时确保在派生类不是您所想的情况下转换失败。例如:

    void foo(Shape * p_shape)
    {
       Triangle * t = dynamic_cast<Triangle *>(p_shape) ;
    
       // if p_shape is a triangle, or derives from triangle,
       // then t is non-NULL, and you can use it
    }
    

    关键是即使 p_shape 不完全是三角形,t 也将是非 NULL,但仍然继承三角形。例如,在这种情况下:

    Shape
     |
     +-- Square
     |
     +-- Triangle
          |
          +-- EquilateralTriangle
          |
          +-- RectangleTriangle
    

    如果 shape 是 Triangle、EquilateralTriangle 或 RectangleTriangle,则 t 不会为 NULL,这比使用常数标记确切类型的初始解决方案强大得多。

    请注意,要让dynamic_cast 在一个类上工作,这个类至少应该有一个虚方法(这通常是在使用dynamic_cast 的树继承层次结构中的情况 i>)

    投掷dynamic_cast

    您可以使用引用而不是使用指针,但是使用引用,因为dynamic_cast 无法返回“失败的引用”,它会抛出一个std::bad_cast,如果需要,您可以捕获:

    void foo(Shape & p_shape)
    {
       Triangle & t = dynamic_cast<Triangle &>(p_shape) ;
    
       // if p_shape is a triangle, or derives from triangle,
       // then the dynamic_cast succeeds.
       // If not, a std::bad_cast is thrown
    }
    

    dynamic_cast滥用?

    需要注意的是,基于指针的非抛出动态转换可能会导致类似开关的代码(但如果您不能依赖虚拟方法,那么您将不得不“切换类型”。 ..):

    void foo(Shape * p_shape)
    {
       if(Triangle * t = dynamic_cast<Triangle *>(p_shape))
       {
          // if p_shape is a triangle, then t is non-NULL,
          // and you can use it
       }
       else if(Square * s = dynamic_cast<Square *>(p_shape))
       {
          // if p_shape is a square, then t is non-NULL
          // and you can use it
       }
       // etc...
    

    与所有“打开类型”代码一样,这很容易出错(如果您忘记处理类型怎么办?),但有时无法避免,因此值得一提。

    (作为好奇的奖励,IIRC,if(type * p = ...) 符号最初被添加到 C++ 中以处理这种情况并减少代码冗长......除非我错过了什么,这种符号在 C# 中是不被授权的)

    RTTI

    总而言之,dynamic_cast 依赖于 RTTI(运行时类型信息),它有时可以被禁用(在工作中,直到几年前,“技术专家”才认为它是不必要的因此必须在我们的构建中禁用...啊啊,“C 类专家”...)

    不要让自己陷入 C 与 C++ 的战争:除非您在非常受限的环境中工作(即嵌入式开发),否则应该激活 RTTI(因为所有其他 C++ 功能,如异常处理)。

    更多关于 RTTI 的信息:http://www.cplusplus.com/reference/std/typeinfo/

    也许我关于 RTTI 的 Stack Overflow 问题会让你感兴趣:C++ RTTI Viable Examples

    【讨论】:

      【解决方案3】:

      你做错了。如果你不得不这样沮丧,你很可能有一个非常严重的设计缺陷。虚拟成员函数应该是解决方案。

      如果你真的必须像这样沮丧,请使用dynamic_cast

      【讨论】:

        【解决方案4】:

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-08-30
          • 1970-01-01
          • 2016-10-11
          • 2021-04-22
          • 1970-01-01
          • 1970-01-01
          • 2017-11-12
          • 1970-01-01
          相关资源
          最近更新 更多