Посоветуйте
От: pilot.net  
Дата: 24.06.15 10:00
Оценка:
Имеем класс в закрытой библиотеке
class  data
{
 ...
    typeData TypeData;
    vector<double> getDoubleValues();
    void setDoubleValues(vector<double>&)
    vector<Float> getFloatValues();
    void setFloatValues(vector<float>&)
 ...
}

И мы пишем постоянно
if ( data.TypeData == ValueDouble)
{
    return func<double>(data.getDoubleValues())
}else
if ( data.TypeData == ValueFloat)
{
    return func<float>(data.getFloatValues())
}

Можно ли это как-то оптимизировать, желательно без макросов
Re: Посоветуйте
От: Mr.Delphist  
Дата: 24.06.15 10:03
Оценка:
Здравствуйте, pilot.net, Вы писали:

PN>Имеем класс в закрытой библиотеке

PN>
PN>class  data
PN>{
PN> ...
PN>    typeData TypeData;
PN>    vector<double> getDoubleValues();
PN>    void setDoubleValues(vector<double>&)
PN>    vector<Float> getFloatValues();
PN>    void setFloatValues(vector<float>&)
PN> ...
PN>}

PN>

PN>И мы пишем постоянно
PN>
PN>if ( data.TypeData == ValueDouble)
PN>{
PN>    return func<double>(data.getDoubleValues())
PN>}else
PN>if ( data.TypeData == ValueFloat)
PN>{
PN>    return func<float>(data.getFloatValues())
PN>}

PN>

PN>Можно ли это как-то оптимизировать, желательно без макросов

Использовать Variant-тип?
Использовать шаблонные getValues<T>()/setValues<T>()?
Re: Посоветуйте
От: velkin Удмуртия http://blogs.rsdn.org/effective/
Дата: 24.06.15 10:19
Оценка:
Здравствуйте, pilot.net, Вы писали:

PN>Можно ли это как-то оптимизировать, желательно без макросов


Зависит от целей. Если рассматривать чистый С++ как язык, то помимо class и struct сразу же вспоминается union. Поскольку я не любитель велосипедов, то могу предложить посмотреть реализацию типа variant в Boost или Qt. Понятное дело Boost гораздо ближе к основе.

http://www.boost.org/doc/libs/1_58_0/doc/html/variant.html
http://www.boost.org/doc/libs/1_58_0/doc/html/any.html

http://doc.qt.io/qt-4.8/qvariant.html
Re: Посоветуйте
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 24.06.15 10:27
Оценка:
Здравствуйте, pilot.net, Вы писали:

PN>Имеем класс в закрытой библиотеке

Выглядит жутко. Шаблон бы тут был в тему.
PN>Можно ли это как-то оптимизировать, желательно без макросов
PN> typeData TypeData;
Какой тип у typeData?
Sic luceat lux!
Re[2]: Посоветуйте
От: pilot.net  
Дата: 24.06.15 10:29
Оценка:
Здравствуйте, Kernan, Вы писали:

<>
PN>> typeData TypeData;
K>Какой тип у typeData?
Enum
Отредактировано 24.06.2015 11:04 Кодт . Предыдущая версия .
Re[2]: Посоветуйте
От: pilot.net  
Дата: 24.06.15 10:33
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

<>
MD>Использовать Variant-тип?
MD>Использовать шаблонные getValues<T>()/setValues<T>()?
Эти слова я и без вас знаю , конкретней пожалуйста
Отредактировано 24.06.2015 11:03 Кодт . Предыдущая версия .
Re[2]: Посоветуйте
От: velkin Удмуртия http://blogs.rsdn.org/effective/
Дата: 24.06.15 10:33
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

MD>Использовать шаблонные getValues<T>()/setValues<T>()?


Это отличный и очень быстрый способ, единственное что на каждый тип T сгенерируется свой класс, что для парадигмы ООП не очень, зато для обобщённого самое оно. Между прочим в STL есть правило, элементы внутри этого, да и не только, контейнера копируются. Стоит очень хорошо подумать, что использовать как тип T, должна ли это быть обычная переменная, или какой-либо вид указателя. Возможно в топике надо было описать какова была изначальная задача и спросить как её можно решить оптимальным способом, а не предлагать решение, которое нужно как-то оптимизировать.
Re[2]: Посоветуйте
От: pilot.net  
Дата: 24.06.15 10:34
Оценка:
Здравствуйте, velkin, Вы писали:

<>
V>http://www.boost.org/doc/libs/1_58_0/doc/html/variant.html
V>http://www.boost.org/doc/libs/1_58_0/doc/html/any.html

Буст нельзя использовать

V>http://doc.qt.io/qt-4.8/qvariant.html

А вот Qt можно но как ?
Отредактировано 24.06.2015 11:05 Кодт . Предыдущая версия .
Re: Посоветуйте
От: uzhas Ниоткуда  
Дата: 24.06.15 10:38
Оценка:
Здравствуйте, pilot.net, Вы писали:

PN>Имеем класс в закрытой библиотеке

я так понимаю, что этот класс нельзя менять. но он очень напоминает класс variant, поэтому тут важно понимать сам подход \ паттерн к обработке данного типа. для этого рекомендую просто ознакомиться с http://www.boost.org/doc/libs/1_58_0/doc/html/variant.html

PN>Можно ли это как-то оптимизировать, желательно без макросов


можно написать вспомогательную диспетчеризующую шаблонную функцию: http://ideone.com/4Tlhkd
можно еще посмотреть в сторону overload:
http://rsdn.ru/forum/cpp/4953155.flat
Автор: alexeiz
Дата: 05.11.12

http://www.boost.org/doc/libs/1_58_0/libs/functional/overloaded_function/doc/html/index.html
Re[3]: Посоветуйте
От: velkin Удмуртия http://blogs.rsdn.org/effective/
Дата: 24.06.15 11:07
Оценка:
Здравствуйте, pilot.net, Вы писали:

V>>http://doc.qt.io/qt-4.8/qvariant.html

PN>А вот Qt можно но как ?

Если можно использовать Qt и не нужна бешеная сверх оптимизированная числодробилка, тогда выберите подходящий контейнер, и используйте внутри QVariant. Чисто для примера QVariantList:

http://doc.qt.io/qt-4.8/qvariant.html#QVariantList-typedef

typedef QVariantList
Synonym for QList<QVariant>.


Для выбора оптимального контейнера почитайте 4-ую главу "Библиотека контейнеров" книги Макса Шлее "Qt 4.8 Профессиональное программирование на C++", ну или вот сейчас нагуглил. Разберитесь с классом QVariant, его возможности очень велики, вашу задачу он уже решил причём в лёгкую. То есть не будет ни vector<double>, ни vector<float>, будет всего лишь один QVariantList, или другой контейнер с QVariant (или QVariant обёрнутый в умный указатель), то есть только одна пара accessor/mutator (get/set).

Доступные типы, плюс в конце видно, что можно использовать пользовательские:
  QVariant
QVariant::Invalid 0 no type
QVariant::BitArray 13 a QBitArray
QVariant::Bitmap 73 a QBitmap
QVariant::Bool 1 a bool
QVariant::Brush 66 a QBrush
QVariant::ByteArray 12 a QByteArray
QVariant::Char 7 a QChar
QVariant::Color 67 a QColor
QVariant::Cursor 74 a QCursor
QVariant::Date 14 a QDate
QVariant::DateTime 16 a QDateTime
QVariant::Double 6 a double
QVariant::EasingCurve 29 a QEasingCurve
QVariant::Font 64 a QFont
QVariant::Hash 28 a QVariantHash
QVariant::Icon 69 a QIcon
QVariant::Image 70 a QImage
QVariant::Int 2 an int
QVariant::KeySequence 76 a QKeySequence
QVariant::Line 23 a QLine
QVariant::LineF 24 a QLineF
QVariant::List 9 a QVariantList
QVariant::Locale 18 a QLocale
QVariant::LongLong 4 a qlonglong
QVariant::Map 8 a QVariantMap
QVariant::Matrix 80 a QMatrix
QVariant::Transform 81 a QTransform
QVariant::Matrix4x4 82 a QMatrix4x4
QVariant::Palette 68 a QPalette
QVariant::Pen 77 a QPen
QVariant::Pixmap 65 a QPixmap
QVariant::Point 25 a QPoint
QVariant::PointArray Polygon a QPointArray
QVariant::PointF 26 a QPointF
QVariant::Polygon 71 a QPolygon
QVariant::Quaternion 86 a QQuaternion
QVariant::Rect 19 a QRect
QVariant::RectF 20 a QRectF
QVariant::RegExp 27 a QRegExp
QVariant::Region 72 a QRegion
QVariant::Size 21 a QSize
QVariant::SizeF 22 a QSizeF
QVariant::SizePolicy 75 a QSizePolicy
QVariant::String 10 a QString
QVariant::StringList 11 a QStringList
QVariant::TextFormat 79 a QTextFormat
QVariant::TextLength 78 a QTextLength
QVariant::Time 15 a QTime
QVariant::UInt 3 a uint
QVariant::ULongLong 5 a qulonglong
QVariant::Url 17 a QUrl
QVariant::Vector2D 83 a QVector2D
QVariant::Vector3D 84 a QVector3D
QVariant::Vector4D 85 a QVector4D
QVariant::UserType 127 Base value for user-defined types.

  QMetaType
QMetaType::Void 0 void
QMetaType::Bool 1 bool
QMetaType::Int 2 int
QMetaType::UInt 3 unsigned int
QMetaType::Double 6 double
QMetaType::QChar 7 QChar
QMetaType::QString 10 QString
QMetaType::QByteArray 12 QByteArray
QMetaType::VoidStar 128 void *
QMetaType::Long 129 long
QMetaType::LongLong 4 LongLong
QMetaType::Short 130 short
QMetaType::Char 131 char
QMetaType::ULong 132 unsigned long
QMetaType::ULongLong 5 ULongLong
QMetaType::UShort 133 unsigned short
QMetaType::UChar 134 unsigned char
QMetaType::Float 135 float
QMetaType::QObjectStar 136 QObject *
QMetaType::QWidgetStar 137 QWidget *
QMetaType::QVariant 138 QVariant
QMetaType::QColorGroup 63 QColorGroup
QMetaType::QCursor 74 QCursor
QMetaType::QDate 14 QDate
QMetaType::QSize 21 QSize
QMetaType::QTime 15 QTime
QMetaType::QVariantList 9 QVariantList
QMetaType::QPolygon 71 QPolygon
QMetaType::QColor 67 QColor
QMetaType::QSizeF 22 QSizeF
QMetaType::QRectF 20 QRectF
QMetaType::QLine 23 QLine
QMetaType::QTextLength 78 QTextLength
QMetaType::QStringList 11 QStringList
QMetaType::QVariantMap 8 QVariantMap
QMetaType::QVariantHash 28 QVariantHash
QMetaType::QIcon 69 QIcon
QMetaType::QPen 77 QPen
QMetaType::QLineF 24 QLineF
QMetaType::QTextFormat 79 QTextFormat
QMetaType::QRect 19 QRect
QMetaType::QPoint 25 QPoint
QMetaType::QUrl 17 QUrl
QMetaType::QRegExp 27 QRegExp
QMetaType::QDateTime 16 QDateTime
QMetaType::QPointF 26 QPointF
QMetaType::QPalette 68 QPalette
QMetaType::QFont 64 QFont
QMetaType::QBrush 66 QBrush
QMetaType::QRegion 72 QRegion
QMetaType::QBitArray 13 QBitArray
QMetaType::QImage 70 QImage
QMetaType::QKeySequence 76 QKeySequence
QMetaType::QSizePolicy 75 QSizePolicy
QMetaType::QPixmap 65 QPixmap
QMetaType::QLocale 18 QLocale
QMetaType::QBitmap 73 QBitmap
QMetaType::QMatrix 80 QMatrix
QMetaType::QTransform 81 QTransform
QMetaType::QMatrix4x4 82 QMatrix4x4
QMetaType::QVector2D 83 QVector2D
QMetaType::QVector3D 84 QVector3D
QMetaType::QVector4D 85 QVector4D
QMetaType::QQuaternion 86 QQuaternion
QMetaType::QEasingCurve 29 QEasingCurve
QMetaType::User 256 Base value for user types

QMetaType::Double 6 double
QMetaType::Float 135 float


Тем не менее, шаблоны C++ это наивысшая скорость и экономия памяти, показатели такие же как у оптимизированного кода Си. В данном же случае получаем наивысший комфорт для программиста жертвуя производительностью и количеством затрачиваемой памяти.
Re[2]: Посоветуйте
От: pilot.net  
Дата: 24.06.15 12:13
Оценка:
Спасибо
U>можно написать вспомогательную диспетчеризующую шаблонную функцию: http://ideone.com/4Tlhkd
Вот это мне понравилось а возможно приделать передачу функций с аргументами в
struct D, чтобы вызывать не только int func(const std::vector<T>& v) но и func2(const std::vector<T0>& v, const std::vector<T0>& v1)
c любыми параметрами ?
U>можно еще посмотреть в сторону overload:
ограничен возможностью VS13
Re[4]: Посоветуйте
От: pilot.net  
Дата: 24.06.15 12:16
Оценка:
V>Если можно использовать Qt и не нужна бешеная сверх оптимизированная числодробилка, тогда выберите подходящий контейнер, и используйте внутри QVariant. Чисто для примера QVariantList:
Спасибо но я думаю этот вариант не подойдет, не вижу плюсов и вектора могут быть с миллионами значений
Re[3]: Посоветуйте
От: uzhas Ниоткуда  
Дата: 24.06.15 14:06
Оценка:
Здравствуйте, pilot.net, Вы писали:

PN>а возможно приделать передачу функций с аргументами в

PN>struct D, чтобы вызывать не только int func(const std::vector<T>& v) но и func2(const std::vector<T0>& v, const std::vector<T0>& v1)
PN>c любыми параметрами ?

опишите подробнее, что хотите получить. лучше кодом покажите. откуда должны браться v и v1 мне неясно

U>>можно еще посмотреть в сторону overload:

PN>ограничен возможностью VS13

это не должно быть помехой
вот этот код у меня собирается и работает в VS2013: http://ideone.com/WPYJhi
Re: Медитировать на мультиметоды
От: LaptevVV Россия  
Дата: 24.06.15 14:34
Оценка:
PN>Можно ли это как-то оптимизировать, желательно без макросов
Про явную проверку типов не написал, кажется, только ленивый — даже я написал...
Но можно читать у Мейерса...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Медитировать на мультиметоды
От: pilot.net  
Дата: 25.06.15 04:30
Оценка:
Здравствуйте, LaptevVV, Вы писали:
LVV>Про явную проверку типов не написал, кажется, только ленивый — даже я написал...
Можно конкретнее ?
Re[4]: Посоветуйте
От: pilot.net  
Дата: 25.06.15 04:40
Оценка:
U>опишите подробнее, что хотите получить. лучше кодом покажите. откуда должны браться v и v1 мне неясно
почти кусок кода:
void Core::checkCorePointBelonging(const Data & desc, const Data& core)
{
    if (desc.isNull()) { return; }
    auto descRef = desc.dataset().findReference();
    if (descRef.isNull()) { return; }
    if (core.isNull()) { return; }
    auto coreRef = core.dataset().findReference();
    if (coreRef.isNull()) { return; }
    if (descRef.format() == FormatDouble)
    {
        auto d = descRef.getDoubleValues();
        if (coreRef.format() == FormatDouble)
        {
            auto c = coreRef.getDoubleValues();
            checkCorePointBelonging<double, double>(d, c, core, desc);
        }
        else
            if (coreRef.format() == FormatFloat)
            {
                auto c = coreRef.getFloatValues();
                checkCorePointBelonging<double, float>(d, c, core, desc);
            }
    }
    else
        if (descRef.format() == FormatFloat)
        {
            auto d = descRef.getFloatValues();
            if (coreRef.format() == FormatDouble)
            {
                auto c = coreRef.getDoubleValues();
                checkCorePointBelonging<float, double>(d, c, core, desc);
            }
            else
                if (coreRef.format() == FormatFloat)
                {
                    auto c = coreRef.getFloatValues();
                    checkCorePointBelonging<float, float>(d, c, core, desc);
                }
        }

}


U>можно еще посмотреть в сторону overload:

Да, работает но как прикрутить его к моим нуждам не могу сообразить
Re[3]: Медитировать на мультиметоды
От: LaptevVV Россия  
Дата: 25.06.15 05:18
Оценка:
LVV>>Про явную проверку типов не написал, кажется, только ленивый — даже я написал...
PN>Можно конкретнее ?
Можно. В моей книжке — глава 10.

Мультиметод — это виртуальная функция, выбираемая при выполнении на основании типа нескольких аргументов [26]. Как сказано в главе 9, в С++ есть два вида полиморфизма: статический и динамический. Статический полиморфизм реализуется перегрузкой функций и шаблонами функций. Виртуальные функции являются воплощением механизма динамического полиморфизма.

К сожалению, два этих вида полиморфизма реализованы в С++ совсем не одинаково. Например, при перегрузке нужный вариант функции выбирается на основании всего списка параметров — они совершенно равноправны. Выбор же виртуальной функции производится по единственному — левому — аргументу. Даже синтаксис вызова виртуальной функции отдает предпочтение объекту obj перед другими аргументами:

obj.function(аргументы)

Говорят, что виртуальная функция способна выполнить одиночную диспетчеризацию. Мультиметоды — это инструмент множественной диспетчеризации. Наиболее распространенный вариант — двойная диспетчеризация, которая означает, что нужная функция выбирается на основании типов двух аргументов. В книге А. Александреску [20] двойная диспетчеризация называется двойным переключением по типу.

Мультиметоды могут понадобиться в ситуациях, когда требуется выполнять некоторую операцию с различными сочетаниями типов параметров. Например, Элджер в [25] приводит пример с операцией сложения различных чисел: целых, действительных, комплексный. Б. Страуструп в [43] описывает ситуацию, когда нужно определять пересечение различных геометрических фигур, Брюс Эккель в [11] рассматривает операцию умножения между матрицами, векторами и скалярами, а Скотт Мейерс в [24] — сталкивающиеся объекты космической игры.

Использование RTTI

Покажем несколько схем реализации мультиметодов. Обычно классы, которые участвуют в работе мультиметодов, являются «родственниками», однако в некоторых случаях возможна реализация и между не родственными классами. Пусть у нас есть базовый абстрактный класс Base и три наследника Derived_a, Derived_b и Derived_c. Нам требуется выполнять некую операцию со всеми возможными сочетаниями параметров. В базовом классе определяется чистый абстрактный метод, который реализуется в производных классах. В общем случае для трех типов A, B и С число сочетаний любых двух типов равно 9:
    1. Derived_a — Derived_a.
    2. Derived_a — Derived_b.
    3. Derived_a — Derived_c.
    4. Derived_b — Derived_a.
    5. Derived_b — Derived_b.
    6. Derived_b — Derived_c.
    7. Derived_c — Derived_a.
    8. Derived_c — Derived_b.
    9. Derived_c — Derived_c
В конкретных ситуациях сочетаний бывает меньше, так как операция может оказаться коммутативной для некоторых сочетаний. Например, операция умножения скаляра и вектора — коммутативна, так же как и операция умножения скаляра на матрицу. Метод, выполняющий двойное переключение по типу, реализуется с параметрами базового класса — используется принцип подстановки. Двойное переключение эмулируются при помощи цепочек if...else с использованием преобразования dymamic_cast<>. Покажем сначала реализацию для двух классов (листинг 10.8), а потом добавим третий.
Листинг 10.8. Двойной диспетчер для двух типов
// определения классов
class Base 
{ public: 
  virtual ~Base()=0; 
  virtual bool Operator(const Base &R) = 0;    // требуемая операция
};
Base::~Base(){}                                // определение деструктора 
class Derived_a: public Base 
{ public:
  virtual bool Operator(const Base &R); 
};
class Derived_b: public Base 
{ public:
  virtual bool Operator(const Base &R);  
};
// реализация методов двойной диспетчеризации
bool Derived_a::Operator(const Base &R)
{ if (const Derived_a *pra = dynamic_cast<const Derived_a*>(&R))
  {  // правый аргумент типа А; обработка варианта "A-A"
     cout << "A-A" << endl; 
     return true; 
  }
  else if (const Derived_b *prb = dynamic_cast<const Derived_b*>(&R))
  { // правый аргумент типа В; обработка варианта "A-В"
    cout << "A-B" << endl; 
    return true; 
  } 
  else throw exception ("Error! Incorrect type argument!");
}
bool Derived_b::Operator(const Base &R)
{ if (const Derived_a *pra = dynamic_cast<const Derived_a*>(&R))
  {  // правый аргумент типа А; обработка варианта "B-A"
     cout << "B-A" << endl; 
     return true; 
  }
  else if (const Derived_b *prb = dynamic_cast<const Derived_b*>(&R))
  {  // правый аргумент типа В; обработка варианта "В-В"
     cout << "B-B" << endl; 
     return true; 
  }
  else throw exception ("Error! Incorrect type argument!");
}

Мультиметоды устроены достаточно просто: проверяется правый операнд и в зависимости от его типа выполняется обработка. Если же правый операнд неизвестного типа, то генерируется исключение. Добавим третий класс и покажем механизм использования оператора typeid() для распознавания типа (листинг 10.9).
Листинг 10.9. Мультиметод нового дополнительного класса
class Derived_c: public Base 
{ public:
  virtual bool Operator(const Base &R);
};
bool Derived_c::Operator(const Base &R) 
{ if (typeid(Derived_a) == typeid(R))
  {  // правый аргумент типа А; обработка варианта "С-A"
     cout << "С-A" << endl; 
     return true; 
  }
  else if (typeid(Derived_b) == typeid(R))
  {  // правый аргумент типа В; обработка варианта "С-В"
     cout << "С-В" << endl; 
     return true; 
  }
  else if (typeid(Derived_c) == typeid(R))
  {  // правый аргумент типа С; обработка варианта "С-С"
     cout << "С-С" << endl; 
     return true; 
  }
  else throw exception ("Error! Incorrect type argument!");    
}

Если требуется, чтобы выполнялась обработка объектов типа C методами классов A и B, нужно модифицировать их, добавив проверку правого аргумента на совпадение с типом С. Но делать это в обязательном порядке нет необходимости — все определяется конкретной задачей. Таким образом, у нас реализовано 7 сочетаний из 9, в чем можно убедиться, выполнив такой фрагмент программы:
Derived_a A; Derived_b B; Derived_c C;
try {
    cout << A.Operator(A) << endl;
    cout << A.Operator(B) << endl;
    cout << B.Operator(A) << endl;
    cout << B.Operator(B) << endl;
    cout << C.Operator(A) << endl;
    cout << C.Operator(B) << endl;
    cout << C.Operator(C) << endl;
    cout << A.Operator(C) << endl;        // вызывает исключение
}
catch(exception &e) 
{ cout << e.what()<< endl; }

Последний оператор вызывает исключение, так как сочетание Derived_a — Derived_c не реализовано.
Задачу мы решили — реализовали двойное переключение по типу. Однако даже начинающий программист понимает, что у этого решения есть недостатки. Например, задействован механизм RTTI, который не является самым эффективным в С++. Но это не самое важное. Гораздо хуже, что нам при добавлении нового класса приходится модифицировать тексты методов. Представьте, что классов у нас хотя бы 7–8, и методов, соответственно, тоже. То есть в каждом методе требуется выполнять до 7–8 однотипных проверок. Добавление нового класса может превратиться в кошмар, поэтому поищем другие пути реализации двойной диспетчеризации.

Листинг 10.9 — явная проверка типа аргумента. Как у вас. Хотя у вас и поле-вектор внутри класса.
Может быть, надо разнести разные типы элементов по разным классам ?
Да еще поставить во главе иерархии базовый абстрактный класс ?
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[3]: Медитировать на мультиметоды
От: LaptevVV Россия  
Дата: 25.06.15 05:22
Оценка:
и виртуальные методы — мультиметоды.

Использование только виртуальных функций

Есть очень нетривиальный способ реализовать двойное переключение по типу с использованием только виртуальных функций. Как обычно, сначала реализуем мультиметоды для двух классов (листинг 10.10), а затем попытаемся добавить третий. Основная идея состоит в том, чтобы реализовать двойную диспетчеризацию как две одиночных, выполняемых последовательно; первый виртуальный вызов определяет динамический тип первого объекта, а второй — второго объекта.
Листинг 10.10. Мультиметоды — только виртуальные функции
// предварительное объявление наследников
class Derived_a;
class Derived_b;
class Derived_c;
// абстрактный базовый класс
class Base 
{ public: 
  virtual ~Base()=0; 
  virtual bool Operator(const Base &R)const = 0;
  virtual bool Operator(const Derived_a &R)const = 0; 
  virtual bool Operator(const Derived_b &R)const = 0; 
};
Base::~Base(){} 
// определение классов-наследников
class Derived_a: public Base 
{ public:
  virtual bool Operator(const Base &R) const 
  { return R.Operator(*this); };     // перенаправление виртуальности
  virtual bool Operator(const Derived_a &R)const
  {  cout << "A-A" << endl; return true; }
  virtual bool Operator(const Derived_b &R)const 
  {  cout << "A-B" << endl; return true;  }
};
class Derived_b: public Base 
{ public:
  virtual bool Operator(const Base &R)const 
  { return R.Operator(*this); };     // перенаправление виртуальности
  virtual bool Operator(const Derived_a &R)const
  {  cout << "B-A" << endl; return true;  }
  virtual bool Operator(const Derived_b &R)const
  {  cout << "B-B" << endl; return true;  }
};

Обратите внимание на реализацию первой функции, которая абсолютно одинакова в обоих классах — именно она реализует двойное переключение по типу:
virtual bool Operator(const Base &R) const 
{ return R.Operator(*this); };

Чтобы было понятно, где и как она работает, рассмотрим фрагмент программы:
Derived_a A; Derived_b B;           // нет необходимости в виртуальности
cout << A.Operator(A) << endl; 
cout << A.Operator(B) << endl; 
cout << B.Operator(A) << endl;
cout << B.Operator(B) << endl;
Base &D = A; 
cout << A.Operator(D) << endl;      // работают 2 виртуальных вызова
cout << B.Operator(D) << endl;      // работают 2 виртуальных вызова
cout << D.Operator(D) << endl;      // работают 2 виртуальных вызова
cout << D.Operator(A) << endl;      // работает 1 виртуальный вызов 
cout << D.Operator(B) << endl;      // работает 1 виртуальный вызов

В первых четырех вызовах нет необходимости в двойной диспетчеризации, так как нет подстановки ссылки на наследника вместо ссылки на базовый класс — вызываются сразу нужные функции. Эти функции и выводят на экран строки, соответствующие типам операндов:
А-А
А-В
В-А
В-В
Следующие три вызова как раз и выполняют двойную диспетчеризацию. А первом варианте мы сначала попадаем в первый метод класса Derived_a, так как тип левого аргумента именно этот. При этом на место параметра-ссылки R подставляется фактический аргумент-ссылка D, инициализированный ссылкой A типа Derived_a, а *this внутри метода статически имеет тип Derived_a. Таким образом, вызов превращается в A.Operator(A), и на экран выводится строка «А-А». Второй вариант вызова сначала приводит нас к первой функции класса B, так как левый аргумент вызова имеет тип Derived_b. Снова на место ссылки R подставляется ссылка D, инициализированная ссылкой типа Derived_a. Однако мы находимся внутри класса Derived_b, поэтому *this имеет тип Derived_b и вызов превращается в A.Operator(B), что мы и наблюдаем при выводе на экран строки «А-В». В третьем случае сначала виртуализируется левый аргумент, и вызов направляется в класс Derived_a, а дальше процесс продолжается по первому варианту и на экран выводится строка «А-А».
Четвертый виртуальный вызов превращается в A.Operator(A), а пятый — в A.Operator(B). Виртуальность работает только один раз — для левого аргумента.
Теперь проделаем то же самое с указателями:
Base *pA = new Derived_a();
Base *pB = new Derived_b();
cout << pA->Operator(*pA) << endl;
cout << pA->Operator(*pB) << endl;
cout << pB->Operator(*pA) << endl;
cout << pB->Operator(*pB) << endl;

Первый вариант, естественно, сначала приводит нас в первый метод класса Derived_a, который вызывает второй метод того же класса с аргументом того же типа. Второй вызов тоже сначала сопоставляется с первым методом класса Derived_a, но поскольку в этом случае на место R поставляется ссылка на объект типа Derived_b, а *this имеет тип Derived_a, то вызывается метод с прототипом
virtual bool Operator(const Derived_a &R) const;
На экране, естественно, получаем строку «В-А». Третий и четвертые варианты, естественно, первый вызов осуществляют из класса Derived_b, а второй — в соответствии с аргументом *this.
Таким образом, методы с параметром типа Base осуществляют то самое двойное переключение по типу.
Этот способ реализации мультиметодов имеет довольно значительные преимущества по сравнению с предыдущим. Во-первых, нет необходимости генерировать исключение при неправильных типах аргументов, поскольку все проверки осуществляет компилятор. Во-вторых, обратите внимание на значительное упрощение текста методов — реализуется только фактически необходимая работа без всяких лишних проверок.
Теперь добавим третий класс и посмотрим, какие изменения придется внести в исходные классы:
class Derived_c: public Base 
{ public:
  virtual bool Operator(const Base &R)const
  { return R.Operator(*this); }  
  virtual bool Operator(const Derived_a &R)const
  {  cout << "C-A" << endl; return true;  }
  virtual bool Operator(const Derived_b &R)const
  {  cout << "C-B" << endl; return true;  }
  virtual bool Operator(const Derived_c &R)const
  {  cout << "C-C" << endl; return true;  }
};

Этот класс реализует нам операцию с левым операндом типа Derived_c. Отметим, что ни класс Derived_a, ни класс Derived_b не работают с аргументом типа Derived_c. Не будем вносить никаких изменений в эти классы, и выполним небольшой фрагмент программы:
Derived_a A; Derived_b B; Derived_c C;
Base &rA = A;
Base &rB = B;
Base &rC = C;
cout << A.Operator(C) << endl;
cout << B.Operator(C) << endl;
cout << rA.Operator(rC) << endl;
cout << rB.Operator(rC) << endl;

Никаких ошибок компиляции нет! Несмотря на то, что мы не определяли в классах Derived_a и Derived_b операции с аргументом типа Derived_c, следующие вызовы не вызывают ошибок компиляции:
cout << A.Operator(C) << endl;
cout << B.Operator(C) << endl;

Причина в том, что в данном случае работает принцип подстановки. Ввиду отсутствия метода с аргументом типа Derived_c выбирается метод класса с параметром-ссылкой базового типа и на его место подставляется ссылка на наследника. Далее выполняется механизм двойной диспетчеризации. Точно так же работают вызовы:
cout << rA.Operator(rC) << endl;
cout << rB.Operator(rC) << endl;

Если нас это устраивает, никаких изменений в классы Derived_a и Derived_b вносить нет необходимости. Если же требуется специальный вид операции A.Operator(C), то нужно добавить соответствующий виртуальный метод в класс Derived_a.
Как уже было сказано, подобная методика работает и для классов, не являющихся родственниками. Построим следующую иерархию классов (листинг 10.11).
Листинг 10.11. Мультиметоды для классов, не являющихся родственниками
class B;                        // объявление корня второй иерархии
class A                         // корень первой иерархии 
{ public:
  virtual ~A(){}
  virtual void Operator(B*)=0; 
};
class D1A;                      // объявления классов первой иерархии
class D2A;
class B                         // корень второй иерархии
{ public: 
  virtual void Operator(D1A& rA) = 0;
  virtual void Operator(D2A& rA) = 0;
};
// классы иерархии А
class D1A: public A
{ public:
  virtual void Operator(B* R) { R->Operator(*this); }
};
class D2A: public A
{ public:
  virtual void Operator(B* R) { R->Operator(*this); }
};
// классы иерархии В
class D1B: public B
{ public:
  virtual void Operator(D1A& rA) {  cout << "D1A!" << endl; };
  virtual void Operator(D2A& rA) {  cout << "D2A!" << endl; };
};
class D2B: public B
{ public:
   virtual void Operator(D1A& rA) {  cout << "D1A!" << endl; };
   virtual void Operator(D2A& rA)  {  cout << "D2A!" << endl; };
};

Две иерархии — А и В — не имеют общих родственников. Тем не менее, методика последовательных виртуальных вызовов, описанная ранее для одной иерархии, работает точно так же. В данном случае все требуемые действия реализованы во второй иерархии, а первая только осуществляет первый шаг двойного переключения по типу. Небольшой фрагмент программы иллюстрирует необходимые переключения:
D1A a1; D2A a2;
B *pb = new D1B();      // указатель на базовый класс <- адрес наследника 
a1.Operator(pb);        // работают 2 виртуальных вызова
a2.Operator(pb);        // работают 2 виртуальных вызова
pb->Operator(a1);       // работает 1 виртуальный вызов 
pb = new D2B();
a1.Operator(pb);        // работают 2 виртуальных вызова
a2.Operator(pb);        // работают 2 виртуальных вызова
pb->Operator(a1);       // работает 1 виртуальный вызов

Двойное переключение по типу — это основа паттерна Visitor (посетитель). Это — один из наиболее трудных для понимания паттернов, недаром в [17] он описан последним.

Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[4]: Медитировать на мультиметоды
От: pilot.net  
Дата: 25.06.15 05:30
Оценка:
Здравствуйте, LaptevVV, Вы писали:
LVV>и виртуальные методы — мультиметоды.
LVV>Двойное переключение по типу — это основа паттерна Visitor (посетитель). Это — один из наиболее трудных для понимания паттернов, недаром в [17] он описан последним.
Хорошие методы, но как это применимо в моем случае ?
Re[5]: Медитировать на мультиметоды
От: LaptevVV Россия  
Дата: 25.06.15 05:38
Оценка:
PN>Хорошие методы, но как это применимо в моем случае ?
А у тебя в одном классе два поля-вектора с разными типами элементов.
и ты их каждый раз проверяешь явно, чтобы вызвать тот или иной метод обработки.
Листинг 10.9 ничего тебе не напоминает?
Мне как раз и бросились в глаза явные проверки типа.
От чего подход мультиметодов позволяет избавиться.
Я б разнес все же на два класса — с разными типами элементов.
А дальше смотрел бы — кто обрабатывает? Нельзя ли сочетать вызывающий метод и вызываемый по примеру мультиметодов?
Тем более, что это можно делать и без родственных связей.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.