在类树中使用动态强制转换是否可以接受

Is it acceptable to use dynamic casting for dynamic converstion within a class tree?

本文关键字:是否 转换 动态      更新时间:2023-10-16

对于大学作业,我正在构建一个类结构,其中部分包括几个Pixel类,每个类都使用特定的颜色空间(如8位灰度、24位RGB等)。

大部分工作由Image::Base&完成,它将使用Pixel::Base&,因此不知道任何Pixel分配的每一侧都有什么特定类型的Pixel

因此,为了允许在未确定的子类型之间进行转换,我使用转换构造函数和operator=,通过基类中的虚拟函数。我看到了两个选项,要么每个类都必须实现to_Grey8()to_RGB()等等——以拥有许多单独的转换函数为代价,使转换构造函数和operator=变小——要么我让接收对象自己进行转换(结果相反)。

出于某种原因,我最初选择了后一种选择。

问题是,转换为RGB(例如)希望复制其他对象的内部值,如果恰好也是RGB对象,但如果其他对象只是Base&,则无法做到这一点,因此,我最终使用dynamic_cast来检查每种需要特殊处理的不同类型的Pixel——随着子类型数量的增长,可能会有很多。

我觉得这种方法可能"不好",所以:
这是dynamic_cast的正确/有效/安全用法吗
如果没有,为什么?为了实现同样的目标,什么是合理的替代方案


仅关注operator=(),以下是定义的重要部分:

class Base
{
public:
    virtual Pixel::Base& operator=( Pixel::Base const& rhs ) = 0;
}
class RGB24 : public Base
{
private:
    unsigned char r, g, b;
public:
    virtual Pixel::RGB24& operator=( Pixel::Base const& rhs );
}
class Grey8: public Base
{
private:
    unsigned char i;
public:
    virtual Pixel::Grey8& operator=( Pixel::Base const& rhs );
}

实现如下所示:

Pixel::Grey8& Grey8::operator=( Pixel::Base const& rhs )
{
    i = rhs.intensity();
    return *this;
}
Pixel::RGB24& RGB24::operator=( Pixel::Base const& rhs )
{
    try {
        auto castrhs = dynamic_cast<Pixel::RGB24 const&>(rhs);
        r = castrhs.r;
        g = castrhs.g;
        b = castrhs.b;
        return *this;
    } catch (std::bad_cast& e) {}
    //TODO other casting blocks will go here as other Pixel classes are added
    //no specific handler worked, so just effectively greyscale it
    r = rhs.intensity();
    g = rhs.intensity();
    b = rhs.intensity();
    return *this;
}

如果你不需要绝对的最佳效率,那么你可以考虑将转换分为两个步骤:将像素类型T1转换为具有最高颜色分辨率的"通用中间"类型(我称之为RGB48),然后从该类型转换为T2。这样,您只需要编写2*N个转换函数,而不需要编写N^2个转换函数。你最终会得到这样的东西:

Pixel::RGB24& RGB24::operator=( Pixel::Base const& rhs )
{
    Pixel::RGB48 rgb48pixel = rhs.toRGB48();
    r = rgb32pixel.r/256; // downsample
    g = rgb32pixel.g/256; // downsample
    b = rgb32pixel.b/256; // downsample
}

所有Pixel类型都必须定义toRGB48()——它在基类中被声明为抽象虚拟方法。

在回答您最初的问题(dynamic_cast是否可以/安全/等)时:有时使用它是合适的,但它通常被认为是一种"代码气味"。但如果你有真正的多重调度,也就是说,你需要一些依赖于两种类型的操作,而不能用一些中间通用表示法分解为两个步骤,那么它实际上是唯一的选择。在其他情况下也可以这样做。它当然是安全/有效的,只是有时它是次优设计的标志。

如果你确实使用动态调度,那么我建议你以以下方式使用它:

 if (auto casthrs = dynamic_cast<Pixel::RGB24 const>(&rhs)) {
     r = castrhs->r;
     g = castrhs->g;
     b = castrhs->b;
     return *this;
 }

(即dynamic_cast是一个指针,而不是引用——如果失败,它将返回0,而不是抛出异常。)这样可以避免异常处理的开销,而且我发现它也更容易阅读。

在我看来,您正在使用的工具(重写equals、使equals成为虚拟函数和动态转换)不太适合这个项目。如果您正在将一种类型的像素转换为另一种类型,那么您可能正在转换潜在的大型位图。在这种情况下,性能是关键。我会在每个位图而不是每个像素的基础上实现转换。如果选角可能很昂贵,那么就不应该按像素进行。

我建议暂时放弃我上面提到的语言的特点。我认为他们在这种情况下没有增加价值,事实上他们正在使解决方案成为问题。使用更原始的编程结构重新思考解决方案。