比 C++ 中 dynamic_cast 更好的解决方案是什么?

4

我有一个类层次结构,是为我的项目设计的,但我不确定如何实现其中的一部分。

这是类层次结构:

class Shape { };

class Colored { // Only pure virtual functions
};

class Square : public Shape { };

class Circle : public Shape { };

class ColoredSquare : public Square, public Colored { };

class ColoredCircle : public Circle, public Colored { };

在我的项目中,我有一个包含不同类型形状的std::vector。然而,为了运行某个算法,我需要将它们放入一个std::vector的彩色对象中(所有这些对象都是不同具体形状的派生类型),因此我需要一种方法在运行时将Square转换为ColoredSquare,将Circle转换为ColoredCircle。
棘手的问题在于,“形状”类与“彩色”类位于不同的库中。
如何最好地完成这个任务?我考虑过进行dynamic_cast检查,但如果有更好的方法,我宁愿采用那种方法。
编辑1:
以下是一个更好的示例:
class Traceable {
    public:
        // All virtual functions
        virtual bool intersect(const Ray& r) = 0;
        // ...
};

class TraceableSphere : public Sphere, public Traceable {
};

class IO {
    public:
        // Reads shapes from a file, constructs new concrete shapes, and returns them to
        // whatever class needs them.
        std::vector<Shape*> shape_reader(std::string file_name);
};

class RayTracer {
    public:
        void init(const std::vector<Shape*>& shapes);
        void run();
    private:
        std::vector<Traceable*> traceable_shapes;
};

void RayTracer::init(const std::vector<Shape*>& shapes) {
    // ??? traceable_shapes <- shapes
}

void RayTracer::run() {
    // Do algorithm
}

如果设计良好,那么基类中应该声明虚方法,需要在派生类中进行特化。为什么要进行强制类型转换? - Rakib
在这里,你应该使用虚继承。 - Rakib
这些形状位于一个“公共”库中,我的项目中的多个库都使用它。每个库对形状执行不同的算法,因此每个库都有自己的形状专业化。 - him61
这些专业化形式是派生的Shape类,它们还继承了一个抽象类,该抽象类强制实现专门的子类功能。 - him61
2
我认为那是不可能的。你可以从Squares构造ColoredSquares等,但你不能将一个东西强制转换成它不是创建的派生类型。 - Rob K
显示剩余8条评论
5个回答

4

You could use the decorator pattern:

class ColorDecorator public Colored
{
    ColorDecorator(Shape* shape): m_shape(shape) {}
    ... //forward/implement whatever you want
};

如果你想将一个正方形存储在彩色向量中,可以使用这种装饰器进行包装。
不过这样做是否有意义是值得怀疑的,这取决于你的设计和其他替代方案。为了保险起见,还应该查看访问者模式(又名双重调度),它可以用于仅访问容器中的子集对象或根据其类型对它们进行不同处理。

@sehe 为什么需要将 Colored 作为基类?仅仅因为 OP 当前尝试使用的方法并不意味着这是必须的或者正确的方法。根据更新后的示例,这个建议似乎相当合适。 - jamessan
我确信这已经接近我所需要的,但我仍然不清楚如何基于形状子类进行区分,因为每个不同的装饰形状将具有不同的交叉实现。 - him61
老实说,我对你试图解决的问题也还有些模糊。考虑到区分包装器内不同形状的问题,我强烈建议采用访问者模式,这也可以传递给包装对象。 - Ulrich Eckhardt
我的意思是,装饰器将针对每个不同的形状具有不同的实现细节(相同的接口)。我有些难以理解。 - him61
除了颜色之外,形状还有虚函数需要根据派生类型进行实现。 - him61
显示剩余4条评论

1
看起来你要以“is-a”风格设计类库,欢迎来到继承地狱。你能详细说明一下你的“算法”吗?通常情况下,如果你需要在对象上进行“类型测试”,那么这是不好的设计,因为这正是你想通过多态避免的。因此,对象应该提供算法使用的适当实现(设计模式:“策略”),高级概念利用“基于策略的类设计”。

0

通过仔细的设计,您可以避免强制转换。特别是要关注SRP。仔细实现方法,使它们使用单个接口来实现单一目标/履行单一职责。您还没有发布有关算法或对象如何使用的任何内容。以下是一个假设的样本设计:

class A {
  public:
        void doSomeThing();
};

class B{
  public:
        void doSomeOtherThing();
};

class C:public A,public B{};

void f1( A* a){
   //some operation
   a->doSomeThing();
   //more operation
}
void f2(B* b){
   //some operation
   b->doSomeOtherThing();
   //more operation

}

int main(int argc, char* argv[])
{
  C c;
  f1(&c);
  f2(&c);

  return 0;
}

注意在不同的上下文中使用对象c。思路是仅使用与特定目的相关的Cinterface。这个例子可以用类来代替函数ff2。例如,您有一些Algorithm类,它们使用继承层次结构中的对象执行某些操作,您应该创建这些类以使它们执行单一职责,这大多数情况下需要使用单一接口,然后您只能创建/传递作为该接口的instance的对象。


0

我认为对你来说最简单和最清晰的解决方案是类似于Chris在最后建议的以下内容。

class Shape {
  virtual Colored *getColored() {
    return NULL;
  }
};

class Colored { // Only pure virtual functions
};

class Square : public Shape { };

class Circle : public Shape { };

class ColoredSquare : public Square, public Colored {
  virtual Colored *getColored() {
    return this;
  }
};

class ColoredCircle : public Circle, public Colored {
  virtual Colored *getColored() {
    return this;
  }
};

我并不完全理解这个语句,即“棘手的问题是‘形状’类和‘有颜色’类在不同的库中。”

这样做为什么不能实现建立一个名为ColoredSquare的类呢?


这会使接口变得混乱,因为它被8个不同的库使用,并且每个形状都有不同的具体实现。 - him61

0

只有在接口的所有实现以不同的方式实现相同的操作时,面向对象编程才有意义。面向对象是关于操作的。您没有展示任何操作,因此我们无法告诉您面向对象编程是否适用于您的问题。如果它没有意义,您不必使用面向对象编程,特别是在C++中,它提供了一些其他管理代码的方法。

至于dynamic_cast - 在设计良好的面向对象代码中,它应该很少出现。如果您在某些情况下确实需要知道具体类型(在实际软件工程中尤其是在维护遗留代码时会出现这种情况),那么它是最好的工具,并且比尝试通过将类似于virtual Concrete* ToConcrete()放入基类来重新实现轮子要干净得多。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接