симметричный шаблон посетителей

Я использую шаблон посетителя для определения набора операций над некоторыми классами.
Некоторые операции являются коммутативными, поэтому я получаю дублирование в коде шаблона посетителя.
Допустим, у меня есть классы A B C и операции: A*A, A*B, A*C, B*A, B*B, B*C, C*A, C*B, C*C.
A*A, B*B, C*C уникальны.
A*B, B*A и их друзья будут иметь дублированный код. Я мог бы реализовать A*B и заставить B*A вызывать A*B, но в итоге я задаюсь вопросом: в каком файле я реализовал операция между A и B снова, в A или в B? (будет около 6 классов, поэтому я буду задавать этот вопрос часто. 15 пар возможных операций)
Существует риск того, что кто-то в будущем сделает бесконечный цикл из A*B, вызывающего B*A, вызывающего A* B при реализации новой операции.
Неестественно иметь соглашение, которое решает, что должно быть реализовано A*B или B*A.
Я мог бы создать третий файл со всеми реализованными функциями, которые вызываются либо A *B и B*A, не похоже на объектно-ориентированный подход.
Как бы вы решили эту проблему?
Спасибо
(я мог бы привести некоторый код, но он длинный и плохо иллюстрирует суть )


person titus    schedule 18.08.2012    source источник


Ответы (2)


Вы правы, вам определенно следует воздержаться от реализации A*B как вызова B*A. Помимо потенциальной возможности создания бесконечной цепочки вызовов, этот подход не отражает симметрии операции в вашем коде, поскольку код не является симметричным.

Лучший подход — реализовать «симметричную» операцию во вспомогательном классе или в качестве функции верхнего уровня, в зависимости от того, что поддерживается вашим языком, а затем сделать так, чтобы A*B и B*A вызывали эту вспомогательную реализацию.

person Sergey Kalinichenko    schedule 18.08.2012
comment
существует указание, что для класса не совсем нормально использовать атрибуты других классов. думаю выбора особо нет - person titus; 18.08.2012
comment
У Скотта Мейера есть отличное обсуждение этой темы в его книге «Более эффективный C++» (статья 31: Виртуальные функции по отношению к более чем одному объекту). Его пример — создание функции столкновения для игры, в которой различные объекты могут сталкиваться в пространстве. Его пример тоже симметричен (неважно, астероид столкнется с космическим кораблем или космический корабль столкнется с астероидом, взрыв один и тот же). Он начинает с шаблона посетителя и постепенно разрабатывает решение, специфичное для C++, основанное на RTTI. - person Sergey Kalinichenko; 18.08.2012
comment
@titus Я почти уверен, что это так (я читал эту книгу в 1997 году, и этот пункт был там). - person Sergey Kalinichenko; 18.08.2012
comment
Я попытался реализовать с помощью RTTI, я использовал typeid().name(), это было очень медленно, затем я попытался создать статическую функцию для каждого подкласса, которая возвращает int, это один из индексов в матрице указателя функции. Это все еще в 3 раза медленнее, чем шаблон посетителя. Вот репозиторий github.com/titusnicolae/comparison/blob/master/rtti. цена за тысячу показов - person titus; 23.08.2012
comment
@titus Это очень любопытно. Ваши функции действительно быстрые, поэтому смена механизма диспетчеризации вызывает 3-кратное замедление? - person Sergey Kalinichenko; 23.08.2012
comment
Пробовал без дополнительного функционала, просто сравнил механизм отправки. Мне нужен этот механизм диспетчеризации для некоторого математического программного обеспечения, я хочу сделать его как можно быстрее. - person titus; 23.08.2012
comment
@titus Оптимизация незавершенного программного обеспечения (также известная как преждевременная оптимизация *) почти всегда является непродуктивным способом тратить свое время. Профилируйте свой код в соответствии с реалистичным сценарием и посмотрите, появляется ли механизм диспетчеризации на экране вашего радара. Если ваши математические операции не очень тривиальны, изменение механизма диспетчеризации будет составлять от одного до двух процентов общего времени. Оптимизация для увеличения скорости на 1..2% почти никогда не стоит затраченных усилий, потому что вы найдете более крупную рыбу для жарки в других частях вашего кода. - person Sergey Kalinichenko; 23.08.2012
comment
Честно говоря, я думал сначала оптимизировать его, потому что позже будет нелегко изменить этот механизм. Я использую несколько очень оптимизированных математических библиотек в своем коде, позже не будет много места для оптимизации. Это правда, я трачу время на оптимизацию этого. Большое спасибо за помощь, я очень ценю это. - person titus; 23.08.2012
comment
@titus Нет уверенности в том, что изменение вашей стратегии отправки позже будет непомерно дорогим: говядина вашей системы заключается в этих небольших фрагментах кода, на которые вы отправляете, а не в самой системе отправки. Поэтому, если вы решите, например, что следует избегать использования RTTI или что вам нужно использовать пользовательскую схему индексации одномерного массива как двумерного, все, что вам нужно сделать, это изменить обвязку фрагментов кода; ценную часть вашего кода не нужно будет менять. - person Sergey Kalinichenko; 23.08.2012

Мое предложение было бы использовать построитель, который будет действовать как построитель параметров

new ParameterBuilder()
    .setFirst( "A" )
    .setSecond( "B" )
    .setThird( "C" )
    ...
    .build();

Тогда у вас будет только один метод, который принимает ParameterBuilder в качестве аргумента.

person Amit Deshpande    schedule 18.08.2012
comment
хм, я мог бы реализовать вещи как Object* result = new MultiplyHelper().addOperand("A").addOperand("B").compute(); - person titus; 18.08.2012
comment
У меня также есть некоторые некоммутативные функции, такие как A/B, использование симметричной конструкции, как указано выше, для несимметричной операции недопустимо. Лучше наоборот. - person titus; 18.08.2012
comment
Построитель параметров предназначен только для получения параметров, что вы с ними делаете, зависит от вас. То, что ты делаешь, тоже хорошо. Я предложил это решение, чтобы сохранить ваши методы нетронутыми и изменить только сигнатуру метода. - person Amit Deshpande; 18.08.2012