Допустимо ли использовать динамическое приведение для динамического общения в дереве классов? - PullRequest
0 голосов
/ 17 февраля 2012

Для университетского задания я строю структуру классов, включающую, в частности, несколько классов Pixel, каждый из которых использует определенное цветовое пространство (например, 8-битный GreyScale, 24-битный RGB и т. Д.).

Большая часть работы выполняется Image::Base&, который будет использовать Pixel::Base& s, поэтому не будет знать, какой конкретный тип Pixel находится на каждой стороне любых Pixel назначений.

Итак, чтобы разрешить преобразование между неопределенными подтипами, я использую конструкторы преобразования и operator= через виртуальные функции из базового класса.Я видел два варианта: либо каждый класс должен реализовывать to_Grey8(), to_RGB() и т. Д. - сделать конструкторы преобразования и operator= небольшими за счет наличия множества отдельных функций преобразования - или я получаю, что принимающий объект выполняетсамо преобразование (с обратными последствиями).

По какой-то причине я изначально выбрал второй вариант.

Проблема в том, что при преобразовании в RGB (например) требуется скопироватьвнутренние значения из другого объекта , если это тоже объект RGB, но он не может этого сделать, если другой объект просто Base&, поэтому в результате я оказалсяиспользуя dynamic_cast s для проверки - потенциально много из них по мере роста числа подтипов - для каждого отдельного типа 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;
}

Ответы [ 2 ]

3 голосов
/ 17 февраля 2012

Если вам не нужна абсолютная наилучшая эффективность, вы можете разбить преобразование на два этапа: преобразовать тип пикселя 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
}

Где все типы пикселей должны определять toRGB48 () - он объявлен как абстрактный виртуальный метод в базовом классе.

В ответ на ваш первоначальный вопрос (динамический ли / безопасен ли / и т. Д.) ?: иногда это целесообразно использовать, но его часто считают «запахом кода». Но в тех случаях, когда у вас подлинная множественная диспетчеризация, т. Е. Вам нужна какая-то операция, которая зависит от двух типов таким образом, что не может быть разложена на два этапа с некоторым промежуточным общим представлением, тогда это действительно единственный вариант. Есть и другие случаи, когда это нормально. Это, безусловно, безопасно / действительно, это просто признак неоптимального дизайна.

Если вы используете динамическую диспетчеризацию, я бы порекомендовал использовать ее следующим образом:

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

(т. Е. Dynamic_cast - указатель, а не ссылка - в случае сбоя он вернет 0, а не вызовет исключение.) Таким образом вы избежите накладных расходов на обработку исключений, и я также считаю, что их легче читать .

1 голос
/ 17 февраля 2012

Мне кажется, что используемые вами инструменты (переопределение equals, создание equals виртуальной функции и динамическое приведение) не подходят для этого проекта. Если вы конвертируете один тип пикселя в другой, то, вероятно, вы конвертируете потенциально большие растровые изображения. Производительность является ключевой в этом случае. Я бы реализовал преобразование для каждого точечного рисунка, а не для каждого пикселя. Если приведение потенциально дорого, то это не должно происходить на пиксель.

Я предлагаю отказаться от функций языка, на который я ссылаюсь выше некоторое время. Я думаю, что они не увеличивают ценность в этом случае, и фактически они делают решение проблемным. Переосмыслите решение, используя более примитивные структуры программирования.

...