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

StackOverflow https://stackoverflow.com/questions/9319235

  •  26-10-2019
  •  | 
  •  

سؤال

For a University assignment I'm building a class structure that includes, in part, several classes of Pixel, each using a specific colour-space (such as 8-bit GreyScale, 24-bit RGB, etc).

Most of the work is done by Image::Base&, which will be using Pixel::Base&s, so won't know what specific type of Pixel is on each side of any Pixel assignments.

So, to allow conversion between the undetermined subtypes I'm making use of conversion constructors and operator=, via virtual functions from the Base class. I saw two options, either every class has to implement to_Grey8(), to_RGB() and so on - making the conversion constructors and operator= small at the cost of having lots of separate conversion functions - or I get the receiving object do the conversion itself (with the reverse consequences).

For some reason, I've initially gone with the latter choice.

The problem is that converting to RGB (for example) want to copy the internal values from the other object if it happens to be also be an RGB object, but it can't do that if the other object is just a Base&, so as a result I've ended up using dynamic_casts to check - potentially lots of them as the number of subtypes grows - for each different type of Pixel that needs special handling.

I get the feeling this approach might be "bad", so:
Is this a correct / valid / safe usage of dynamic_cast?
If not, why? And what's a reasonable alternative to achieve the same goals?


Just focusing on operator=(), here's the important bits of the definitions:

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

With the implementations looking like this:

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;
}
هل كانت مفيدة؟

المحلول

If you don't need absolute-best efficiency, then you might consider breaking the conversion into two steps: convert pixel type T1 to some "universal intermediate" type that has the highest color resolution (I'll call it RGB48), and then convert from that type to T2. That way, you only need to write 2*N conversion functions, rather than N^2 conversion functions. You'd end up with something like:

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
}

Where all Pixel types must define toRGB48() -- it is declared as an abstract virtual method in the base class.

In answer to your original question (is dynamic_cast ok/safe/etc)?: sometimes it's appropriate to use, but it's often considered a "code smell." But in cases where you have genuine multiple dispatch -- i.e., you need some operation that depends on two types in a way that can't be decomposed into two steps with some intermediate common representation, then it's really the only option. There are other cases where it's ok to do as well. It's certainly safe/valid, it's just sometimes a sign of suboptimal design.

If you do use dynamic dispatch, then I'd recommend using it the following way:

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

(i.e., dynamic_cast a pointer, rather than a reference -- this will return 0 if it fails, rather than throwing an exception.) That way you avoid the overhead of exception handling, and I find it easier to read, too.

نصائح أخرى

It seems to me that the tools you are using (overriding equals, making equals a virtual function and dynamic casting) are not a good fit for this project. If you are converting one type of pixel to another then presumably you are converting potentially large bitmaps. Performance is key in this case. I would be implementing the conversion on a per bitmap rather than a per pixel basis. If the casting is potentially expensive then it shouldn't happen per pixel.

I propose discarding the features of the language that I refer to above for a while. I think they are not adding value in this case and in fact they are making the solution problematic. Rethink the solution using more primitive programming structures.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top