Реализация "стратегий"
От: tdiff  
Дата: 17.07.14 07:02
Оценка:
Привет всем,

Хочу понять, в чём различие и когда стоить выбирать один из следующих подходов для реализации настраиваемых стратегий/политик в классе:

Вариант А
class Iface { ... };
class IfaceImpl1 : public Iface { ... };
class IfaceImpl2 : public Iface { ... };
class Klass
{
  Iface* strategy;
}


Вариант Б
template <class Strategy>
class Klass
{
  Strategy s;
}


?

Допустим, стратегия задаётся для экземпляра класса один раз и не меняется во время исполнения.
Re: Реализация "стратегий"
От: jazzer Россия Skype: enerjazzer
Дата: 17.07.14 07:16
Оценка:
Здравствуйте, tdiff, Вы писали:

T>Допустим, стратегия задаётся для экземпляра класса один раз и не меняется во время исполнения.


В варианте Б она задается во время компиляции. Это тебе подходит?
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Реализация "стратегий"
От: Sashaka Россия  
Дата: 17.07.14 07:17
Оценка:
Здравствуйте, tdiff, Вы писали:

T>Привет всем,


T>Хочу понять, в чём различие и когда стоить выбирать один из следующих подходов для реализации настраиваемых стратегий/политик в классе:

T>Допустим, стратегия задаётся для экземпляра класса один раз и не меняется во время исполнения.

ты сам почти ответил на свой вопрос:
runtime — динамический полиморфизм ( в твоем случае интерфейсы, но можно и по другому ), очевидно, имеет некоторый performance impact
compile time — статический полиморфизм ( в твоем случае шаблоны, но можно и по другому )
Re[2]: Реализация "стратегий"
От: tdiff  
Дата: 17.07.14 07:21
Оценка:
Здравствуйте, jazzer, Вы писали:

J>В варианте Б она задается во время компиляции. Это тебе подходит?


А когда это может подходить\не подходить?
Тут дело только в некоторой разнице в скорости выполнения?
Re[3]: Реализация "стратегий"
От: jazzer Россия Skype: enerjazzer
Дата: 17.07.14 07:26
Оценка:
Здравствуйте, tdiff, Вы писали:

T>Здравствуйте, jazzer, Вы писали:


J>>В варианте Б она задается во время компиляции. Это тебе подходит?


T>А когда это может подходить\не подходить?

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

T>Тут дело только в некоторой разнице в скорости выполнения?

Не только. Виртуальный интерфейс задает интерфейс бинарный (с оговорками). Например, дополнительные реализации интерфейса могут загружаться в виде плагинов, написанных вообще третьими лицами.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Реализация "стратегий"
От: tdiff  
Дата: 17.07.14 07:28
Оценка:
Здравствуйте, Sashaka, Вы писали:

S>Здравствуйте, tdiff, Вы писали:


T>>Привет всем,


T>>Хочу понять, в чём различие и когда стоить выбирать один из следующих подходов для реализации настраиваемых стратегий/политик в классе:

T>>Допустим, стратегия задаётся для экземпляра класса один раз и не меняется во время исполнения.

S>ты сам почти ответил на свой вопрос:

S>runtime — динамический полиморфизм ( в твоем случае интерфейсы, но можно и по другому ), очевидно, имеет некоторый performance impact
S>compile time — статический полиморфизм ( в твоем случае шаблоны, но можно и по другому )

Ну то есть в такой ситуации дефолтное решение — использовать шаблоны? (результат тот же, а выполняется на сколько-то быстрей)
Я хочу, чтобы получилась какая-то картинка с плюсами и минусами различных решений.
Re[4]: Реализация "стратегий"
От: tdiff  
Дата: 17.07.14 07:37
Оценка:
Здравствуйте, jazzer, Вы писали:

T>>А когда это может подходить\не подходить?


J>Тут еще зависит от того, где ты проведешь границу настройки. Если она проводится достаточно крупно (ты выбираешь сразу семейство классов) — то между этими классами может быть невиртуальное взаимодействие.

Извини, ты не мог бы чуть подробнее объяснить?
Почему ты акцентируешь внимание на невиртуальном взаимодействии (имеются в виду шаблоны?) именно в случае выбора сеймейства классов?
Re[5]: Реализация "стратегий"
От: jazzer Россия Skype: enerjazzer
Дата: 17.07.14 07:47
Оценка:
Здравствуйте, tdiff, Вы писали:

T>Здравствуйте, jazzer, Вы писали:


T>>>А когда это может подходить\не подходить?


J>>Тут еще зависит от того, где ты проведешь границу настройки. Если она проводится достаточно крупно (ты выбираешь сразу семейство классов) — то между этими классами может быть невиртуальное взаимодействие.

T>Извини, ты не мог бы чуть подробнее объяснить?
T>Почему ты акцентируешь внимание на невиртуальном взаимодействии (имеются в виду шаблоны?) именно в случае выбора сеймейства классов?

В контексте одного класса все виртуальные методы вызываются медленнее, чем если бы они были невиртуальными.
Т.е. если у тебя много классов, которые обмениваются множеством виртуальных вызовов, удар по быстродействию может стать ощутимым (если функций много, но каждая делает мало).
Но если реально стратегия — это семейство классов, а не сочетания каждый-с-каждым (т.е. это классы А1,Б1,В1 или А2,Б2,В2, а сочетание А1,Б2,В3 невозможно), то ты можешь предоставить наружу общий виртуальный фасад такого сборного компонента, а общение между классами в рамках одной стратегии сделать невиртуальным.
Тогда виртуальные потери будут только на входе и ими, скорее всего, можно будет пренебречь (в предположении, что компонент в целом совершает достаточно работы, чтоб разница в вызове была заметна).
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[6]: Реализация "стратегий"
От: tdiff  
Дата: 17.07.14 08:09
Оценка:
Здравствуйте, jazzer, Вы писали:

J>В контексте одного класса все виртуальные методы вызываются медленнее, чем если бы они были невиртуальными.

J>Т.е. если у тебя много классов, которые обмениваются множеством виртуальных вызовов, удар по быстродействию может стать ощутимым (если функций много, но каждая делает мало).
J>Но если реально стратегия — это семейство классов, а не сочетания каждый-с-каждым (т.е. это классы А1,Б1,В1 или А2,Б2,В2, а сочетание А1,Б2,В3 невозможно), то ты можешь предоставить наружу общий виртуальный фасад такого сборного компонента, а общение между классами в рамках одной стратегии сделать невиртуальным.
J>Тогда виртуальные потери будут только на входе и ими, скорее всего, можно будет пренебречь (в предположении, что компонент в целом совершает достаточно работы, чтоб разница в вызове была заметна).


Понятно, спасибо
Re: Реализация "стратегий"
От: saf_e  
Дата: 17.07.14 08:49
Оценка: 36 (2) +3
Здравствуйте, tdiff, Вы писали:

T>Привет всем,


T>Хочу понять, в чём различие и когда стоить выбирать один из следующих подходов для реализации настраиваемых стратегий/политик в классе:


T>Вариант А

T>
T>class Iface { ... };
T>class IfaceImpl1 : public Iface { ... };
T>class IfaceImpl2 : public Iface { ... };
T>class Klass
T>{
T>  Iface* strategy;
T>}

T>


T>Вариант Б

T>
T>template <class Strategy>
T>class Klass
T>{
T>  Strategy s;
T>}
T>


T>?


T>Допустим, стратегия задаётся для экземпляра класса один раз и не меняется во время исполнения.


Главный минус второго подхода, это то, что от выбранной стратегии меняется тип класса, что в общем случае сильно мешает.

Пример: std::vector и его allocator. В бощем случае вам все равно где контейнер хранит свои элементы, внешний интерфейс у него один и тот же. Однако из-за того, что алокатор является частью типа, вы не можете написать ф-цию f(std::vector<int>) и принимать любые вектора.

Это огромный минус темплейтных стратегий.

Пример другой реализации: std::shared_ptr у него deleter не является частью сигнатуры, и вам вобщем-то все равно как будет разрушаться объект, до тех пор пока выполняется контракт на shared_ptr.

Я бы всегда выбрирал первый вариант, за исключением, когда в явном виде нужен второй (производительность, явная зависимость на тип стратегии и т.п.)
Re[2]: Реализация "стратегий"
От: tdiff  
Дата: 17.07.14 09:31
Оценка:
Здравствуйте, saf_e, Вы писали:


_>Пример: std::vector и его allocator. В бощем случае вам все равно где контейнер хранит свои элементы, внешний интерфейс у него один и тот же. Однако из-за того, что алокатор является частью типа, вы не можете написать ф-цию f(std::vector<int>) и принимать любые вектора.


_>Это огромный минус темплейтных стратегий.


Ну наверно можно как-то написать шаблонную f(), которая будет принимать что-то, ведущее себя как vector<int> (а не vector<double>)?
Но даже если можно, это здорово всё усложнит наверно.
Re[3]: Реализация "стратегий"
От: saf_e  
Дата: 17.07.14 09:34
Оценка:
Здравствуйте, tdiff, Вы писали:

T>Здравствуйте, saf_e, Вы писали:



_>>Пример: std::vector и его allocator. В бощем случае вам все равно где контейнер хранит свои элементы, внешний интерфейс у него один и тот же. Однако из-за того, что алокатор является частью типа, вы не можете написать ф-цию f(std::vector<int>) и принимать любые вектора.


_>>Это огромный минус темплейтных стратегий.


T>Ну наверно можно как-то написать шаблонную f(), которая будет принимать что-то, ведущее себя как vector<int> (а не vector<double>)?

T>Но даже если можно, это здорово всё усложнит наверно.

Да, в этом случае придется делать много темплейтного кода. Вы рискуете закончить написанием полностью инлайнового приложения
Re[6]: Реализация "стратегий"
От: tdiff  
Дата: 17.07.14 09:39
Оценка:
Здравствуйте, jazzer, Вы писали:

J>В контексте одного класса все виртуальные методы вызываются медленнее, чем если бы они были невиртуальными.

J>Т.е. если у тебя много классов, которые обмениваются множеством виртуальных вызовов, удар по быстродействию может стать ощутимым (если функций много, но каждая делает мало).
J>Но если реально стратегия — это семейство классов, а не сочетания каждый-с-каждым (т.е. это классы А1,Б1,В1 или А2,Б2,В2, а сочетание А1,Б2,В3 невозможно), то ты можешь предоставить наружу общий виртуальный фасад такого сборного компонента, а общение между классами в рамках одной стратегии сделать невиртуальным.
J>Тогда виртуальные потери будут только на входе и ими, скорее всего, можно будет пренебречь (в предположении, что компонент в целом совершает достаточно работы, чтоб разница в вызове была заметна).

Хотя ещё вопрос. Если я правильно тебя понял, ты имеешь в виду примерно такой вариант:


class IFacade
{
  virtual void f() =0;
};

template<class A, class B, class C> 
class Klass : public IFacade
{
  void f() { }
}

int main()
{
  IFacade* fac;
  fac = new Klass<A1, B1, C1>();
  fac = new Klass<A2, B2, C2>();
}



Ты говоришь, что такое имеет смысл, если стратегия — это некая заданная комбинация классов.
Но чем плохо в таком варианте сделать

fac = new Klass<A1, B2, C3>()


?
Re[7]: Реализация "стратегий"
От: jazzer Россия Skype: enerjazzer
Дата: 17.07.14 09:57
Оценка:
Здравствуйте, tdiff, Вы писали:

T>Ты говоришь, что такое имеет смысл, если стратегия — это некая заданная комбинация классов.

T>Но чем плохо в таком варианте сделать

T>
T>fac = new Klass<A1, B2, C3>()
T>


ничего не плохо.
Я имел в виду, что во время выполнения выбирается конкретная комбинация (и ты ее прописываешь на этапе компиляции, как в коде выше), а не каждый из А,Б,В. Сорри, если криво сформулировал.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: Реализация "стратегий"
От: jazzer Россия Skype: enerjazzer
Дата: 17.07.14 10:34
Оценка:
Здравствуйте, tdiff, Вы писали:

T>Здравствуйте, saf_e, Вы писали:



_>>Пример: std::vector и его allocator. В бощем случае вам все равно где контейнер хранит свои элементы, внешний интерфейс у него один и тот же. Однако из-за того, что алокатор является частью типа, вы не можете написать ф-цию f(std::vector<int>) и принимать любые вектора.


_>>Это огромный минус темплейтных стратегий.


T>Ну наверно можно как-то написать шаблонную f(), которая будет принимать что-то, ведущее себя как vector<int> (а не vector<double>)?

T>Но даже если можно, это здорово всё усложнит наверно.

Если ты собираешься там менять capacity (т.е. заниматься перераспределением памяти) — то нет, так как нужно в явном виде обращаться к аллокатору.
Если же ничего такого не предполагается — то можно воспользоваться усеченным интерфейсом, который зависеть от аллокатора не будет.
А если и размер (даже в пределах capacity) не предполагается менять, то этот усеченный интерфейс уменьшается до совсем простого и всем известного — это итераторы (а в случае вектора — это вообще голые указатели).
Более того, это проблему осознали довольно давно и появилась целая идиома SCARY-итераторов (поддержанная Строуструпом, кстати):

The paper N2913, titled SCARY Iterator Assignment and Initialization, proposed a requirement that a standard container's iterator types have no dependency on any type argument apart from the container's value_type, difference_type, pointer type, and const_pointer type. In particular, according to the proposal, the types of a standard container's iterators should not depend on the container's key_compare, hasher, key_equal, or allocator types.

That paper demonstrated that SCARY operations were crucial to the performant implementation of common design patterns using STL components. It showed that implementations that support SCARY operations reduce object code bloat by eliminating redundant specializations of iterator and algorithm templates.

(это цитата из буста)

Все началось со статьи N2911 — "Minimizing Dependencies within Generic Classes for Faster and Smaller Programs"
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2911.pdf

Дальше была статья конкретно про итераторы: N2913 "SCARY Iterator Assignment and Initialization", замененная позже N2980
http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2009/n2980.pdf
наверное, были ревизии и дальше, лень искать, но суть должна быть понятна — надо делать отдельные усеченные интерфейсы, которые будут зависеть от меньшего числа шаблонных параметров, чем полный интерфейс.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[8]: Реализация "стратегий"
От: tdiff  
Дата: 17.07.14 11:07
Оценка:
Здравствуйте, jazzer, Вы писали:

J>ничего не плохо.

J>Я имел в виду, что во время выполнения выбирается конкретная комбинация (и ты ее прописываешь на этапе компиляции, как в коде выше), а не каждый из А,Б,В. Сорри, если криво сформулировал.

Нет, я просто неправильно понял
Re[4]: Реализация "стратегий"
От: saf_e  
Дата: 17.07.14 11:08
Оценка:
Здравствуйте, jazzer, Вы писали:


J>Если ты собираешься там менять capacity (т.е. заниматься перераспределением памяти) — то нет, так как нужно в явном виде обращаться к аллокатору.

J>Если же ничего такого не предполагается — то можно воспользоваться усеченным интерфейсом, который зависеть от аллокатора не будет.
J>А если и размер (даже в пределах capacity) не предполагается менять, то этот усеченный интерфейс уменьшается до совсем простого и всем известного — это итераторы (а в случае вектора — это вообще голые указатели).
J>Более того, это проблему осознали довольно давно и появилась целая идиома SCARY-итераторов (поддержанная Строуструпом, кстати):

Не понимаю почему, меня как пользователя, должно интересовать зависит ли имплементация конкретной ф-ции от стратегии (или фазы луны). До тех пор, конечно, пока я не захочу в эти детали вникнуть, но к сути вопроса это отношения не имеет.

Еще раз, стратегия обеспечивает вариативность поведения класса в рамках его интерфейса, и в общем случае мне все равно что там происходит, пока соблюдается контракт.
Re[5]: Реализация "стратегий"
От: jazzer Россия Skype: enerjazzer
Дата: 17.07.14 14:23
Оценка:
Здравствуйте, saf_e, Вы писали:

_>Не понимаю почему, меня как пользователя, должно интересовать зависит ли имплементация конкретной ф-ции от стратегии (или фазы луны). До тех пор, конечно, пока я не захочу в эти детали вникнуть, но к сути вопроса это отношения не имеет.


_>Еще раз, стратегия обеспечивает вариативность поведения класса в рамках его интерфейса, и в общем случае мне все равно что там происходит, пока соблюдается контракт.


Если честно, я не понял, к чему это было, не пояснишь?
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[6]: Реализация "стратегий"
От: saf_e  
Дата: 17.07.14 14:59
Оценка: +1
Здравствуйте, jazzer, Вы писали:

J>Здравствуйте, saf_e, Вы писали:


_>>Не понимаю почему, меня как пользователя, должно интересовать зависит ли имплементация конкретной ф-ции от стратегии (или фазы луны). До тех пор, конечно, пока я не захочу в эти детали вникнуть, но к сути вопроса это отношения не имеет.


_>>Еще раз, стратегия обеспечивает вариативность поведения класса в рамках его интерфейса, и в общем случае мне все равно что там происходит, пока соблюдается контракт.


J>Если честно, я не понял, к чему это было, не пояснишь?


J>Если ты собираешься там менять capacity (т.е. заниматься перераспределением памяти) — то нет, так как нужно в явном виде обращаться к аллокатору.

J>Если же ничего такого не предполагается — то можно воспользоваться усеченным интерфейсом, который зависеть от аллокатора не будет.


Может я чего, не понял, но тут идет укзание на то, что итерфейс класса должен зависеть от стратегии, я и написал, что меня как пользователя это не устраивает.
Re[7]: Реализация "стратегий"
От: jazzer Россия Skype: enerjazzer
Дата: 17.07.14 15:52
Оценка: +1
Здравствуйте, saf_e, Вы писали:

J>>Если ты собираешься там менять capacity (т.е. заниматься перераспределением памяти) — то нет, так как нужно в явном виде обращаться к аллокатору.

J>>Если же ничего такого не предполагается — то можно воспользоваться усеченным интерфейсом, который зависеть от аллокатора не будет.

_>Может я чего, не понял, но тут идет укзание на то, что итерфейс класса должен зависеть от стратегии, я и написал, что меня как пользователя это не устраивает.


А меня не устраивают виртуальные вызовы, я хочу, чтобы все было заинлайнено по максимуму

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

Тут просто нужен баланс, основанный на реальных сценариях использования.
По мне так баланс в shared_ptr найден правильный, потому что обычный сценарий использования — это создали, наделали копий, потом когда-то, очень нескоро, все многочисленные копии наконец сдохли и дергнули виртуальный deleter. Ну и фиг с ним, это очень редкая операция.
Аналогично можно было бы поступить и с вектором, вообще говоря — аллокаций в векторе очень мало, так как размер аллокации обычно растет экспоненциально. Ну и плюс тормоза виртуального вызова по сравнению с тормозами самой аллокации, думаю, не были бы заметны.
А вот в случае контейнеров с узлами, когда аллокация происходит на каждый добавленный элемент — тут уже прятать аллокатор под виртуальный интерфейс стремновато — уж слишком часты в таких контейнерах аллокации, плюс они могут быть крайне быстрыми, если используется одноразовый локальный аллокатор-арена без логики внутри (вернее, с одним ифом — не вылезли ли за пределы — и все).

Но моя мысль была в другом. Даже в интерфейсе, из которого потроха торчат наружу по соображениям скорости, можно выделить подынтерфейс, не зависящий от этих потрохов. Потому что вряд ли ВСЕ потроха должны торчать из ВСЕХ методов.
Например, std::vector<T, A> вполне мог бы предоставлять интерфейс std::fixed_capacity_vector<T>, который не зависит от А и способен менять размеры в рамках своей capacity. И тогда можно было бы писать функции, не меняющие размер (вернее, capacity) вектора, в терминах fixed_capacity_vector<T>, и они не зависели бы от аллокатора.

Ну а std::fixed_size_container<T> и так уже есть — это итераторы.
Только они должны быть SCARY (то есть не зависеть от тех самых потрохов).
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.