Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов, Яндекс

17,348 views

Published on

Основываясь на опыте разработки Крипты, Дмитрий рассмотрит средства реализации статического и динамического полиморфизма в C++, а также некоторые их паттерны и антипаттерны.

Published in: Internet
  • Be the first to comment

Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов, Яндекс

  1. 1. 1
  2. 2. 2 Некоторые паттерны реализации полиморфного поведения в C++ Дмитрий Леванов Ведущий разработчик Крипта
  3. 3. 3 Крипта
  4. 4. 4 Разработка Крипты  Много логов в разных форматах  Сложные цепочки обработки  Высокие требования к производительности  Много одинаковой похожей логики  Хочется делать всё однообразно
  5. 5. 5 Полиморфизм
  6. 6. 6 Полиморфизм  Способ поставить в соответствие некой грамматической конструкции контекстно- зависимую семантику или, по-русски:  Текст программы [почти] один и тот же, а смысл разный
  7. 7. 7 Шаблоны проектирования  Abstract server/client  Adapter  Decorator  Proxy  Template method  Command  Strategy  …
  8. 8. 8 Виртуальный полиморфизм struct Base { virtual void do() { std::cout << “base”; } }; struct Derived : public Base { virtual void do() override { std::cout << “derived”; } }; std::unique_ptr<Base> b(new Derived); b->do(); // derived  Явные интерфейсы  Типобезопасно  Работают фичи, зависящие от _vptr
  9. 9. 9 Виртуальный полиморфизм: минусы  Медленный вызов методов  Расход памяти на объекты  Требует наличия иерархии классов  Приходится иметь дело с T* или T&  Грабли с виртуальными методами  Инвариантность параметров
  10. 10. 10 Виртуальный полиморфизм: минусы  Медленный вызов методов  Расход памяти на объекты  Требует наличия иерархии классов  Приходится иметь дело с T* или T&  Грабли с виртуальными методами  Инвариантность параметров  Во многих случаях это не критично
  11. 11. 11 Виртуальный полиморфизм: минусы  Медленный вызов методов  Расход памяти на объекты  Требует наличия иерархии классов  Приходится иметь дело с T* или T&  Грабли с виртуальными методами  Инвариантность параметров  Во многих случаях это не критично  Но иногда может стать проблемой
  12. 12. 12 Простой пример
  13. 13. 13 Простой пример struct Handler { virtual void handle(int) = 0; virtual ~Handler() {} }; void for_each(const std::vector<int>& v, const Handler& h) { for (int i : v) { h.handle(i); } } //... std::vector<int> vect = {1,2,3}; MyHandler handler; for_each(vect, handler);
  14. 14. 14 То же самое, но лучше template<typename Handler> void for_each(const std::vector<int>& v, const Handler& h) { for (int i : v) { h.handle(i); } } //... std::vector<int> vect = {1,2,3}; MyHandler handler; for_each(vect, handler);
  15. 15. 15 То же самое, но лучше template<typename Handler> void for_each(const std::vector<int>& v, Handler& h) { for (int i : v) { h.handle(i); } } struct Sum { int sum = 0; void handle(int i) { sum += i; } }; std::vector<int> vect(1000000000); Sum handler; for_each(vect, handler);
  16. 16. 16 То же самое, но лучше template<typename Handler> void for_each(const std::vector<int>& v, Handler& h) { for (int i : v) { h.handle(i); } } struct Sum { int sum = 0; void handle(int i) { sum += i; } }; std::vector<int> vect(1000000000); Sum handler; for_each(vect, handler); Бесплатное ускорение x9.2 (-5 тактов на вызов)
  17. 17. 17 То же самое, но лучше template<typename Handler> void for_each(const std::vector<int>& v, Handler& h) { for (int i : v) { h.handle(i); } } struct PositivesSum { int sum = 0; void handle(int i) { if (i > 0) sum += i; } }; std::vector<int> vect(1000000000); PositivesSum handler; for_each(vect, handler); Бесплатное ускорение x2.8
  18. 18. 18 Продолжаем улучшать template<typename Handler> void for_each(const std::vector<int>& v, const Handler& h) { for (int i : v) { h(i); } } //... std::vector<int> vect = {1,2,3}; MyHandler handler; for_each(vect, handler);
  19. 19. 19 Совсем хорошо template<typename Handler> void for_each(const std::vector<int>& v, const Handler& h) { for (int i : v) { h(i); } } //... std::vector<int> vect = {1,2,3}; for_each(vect, [](int i){std::cout << i; });
  20. 20. 20 Или так template<typename Handler> void for_each(const std::vector<int>& v) { for (int i : v) { Handler::handle(i); } } //... std::vector<int> vect = {1,2,3}; for_each<MyHandler>(vect);
  21. 21. 21 Статический полиморфизм: плюсы  Нет накладных расходов на вызов методов  Не надо наследоваться  Не надо иметь дело с указателями  Контрвариантность параметров  Можно использовать лямбды
  22. 22. 22 Статический полиморфизм: минусы  Нельзя положить в коллекцию  Сложно проверять правильность кода  Медленно компилируется  Может распухнуть бинарник  Нельзя явно задать интерфейсы  Мало помощи от IDE
  23. 23. 23 Синтаксис иногда довольно странный… this->do();
  24. 24. 24 Синтаксис иногда довольно странный… this->do(); typename T::iter f(typename T::iter i);
  25. 25. 25 Синтаксис иногда довольно странный… this->do(); typename T::iter f(typename T::iter i); this->template do<T>(); // WTF???
  26. 26. 26 Curiously Recurring Template Pattern
  27. 27. 27 Template method
  28. 28. 28 Template method struct Game { void play() { while(!end()) { makeTurn(); switchActivePlayer(); } } virtual bool end() = 0; virtual void makeTurn() = 0; virtual void switchActivePlayer() = 0; virtual ~Game() {} };
  29. 29. 29 «Виртуальный» вызов без virtual a.k.a. Curiously Recurring Template Pattern
  30. 30. 30 «Виртуальный» вызов без virtual a.k.a. Curiously Recurring Template Pattern template<typename Derived> class Game { void end() { static_cast<Derived*>(this)->end(); } };
  31. 31. 31 «Виртуальный» вызов без virtual a.k.a. Curiously Recurring Template Pattern template<typename Derived> class Game { void end() { static_cast<Derived*>(this)->end(); } }; class Chess : public Game<Chess> { void end() {/*Check if king surrounded*/} };
  32. 32. 32 «Виртуальный» вызов без virtual a.k.a. Curiously Recurring Template Pattern template<typename Derived> class Game { void end() { static_cast<Derived*>(this)->end(); } }; class Chess : public Game<Chess> { void end() {/*Check if king surrounded*/} }; std::unique_ptr<Game<Chess>> game(new Chess); game->play(); // calls Chess::end() inside
  33. 33. 33 CRTP  «Виртуальный» метод может быть статическим  Может работать в ~7 раз быстрее виртуальной версии* ⃰ http://bit.ly/crtp_vs_virtual
  34. 34. 34 Tag dispatching
  35. 35. 35 Tag dispatching template <class InputIter, class Dist> void advance(InputIter& it, Dist n); template <class InputIter, class Dist> void advance(InputIter& i, Dist n) { while (n--) ++i; } template <class RndAcsIter, class Dist> void advance(RndAcsIter& i, Dist n) { i += n; }
  36. 36. 36 Tag dispatching template <class InputIter, class Dist> void advance(InputIter& it, Dist n); template <class InputIter, class Dist> void advance(InputIter& i, Dist n, input_iter_tag) { while (n--) ++i; } template <class RndAcsIter, class Dist> void advance(RndAcsIter& i, Dist n, rnd_acs_iter_tag) { i += n; }
  37. 37. 37 Tag dispatching template <class InputIter, class Dist> void advance(InputIter& it, Dist n) { typename iter_traits<InputIter>::iter_category cat; advance(i, n, cat); } template <class InputIter, class Dist> void advance(InputIter& i, Dist n, input_iter_tag) { while (n--) ++i; } template <class RndAcsIter, class Dist> void advance(RndAcsIter& i, Dist n, rnd_acs_iter_tag) { i += n; }
  38. 38. 38 External polymorphism
  39. 39. 39 Задача  Пишем инструмент для отладки  Есть множество объектов, не связанных какой-либо иерархией  Хотим сложить их в одну коллекцию, проитерироваться по ней, и распечатать содержимое объектов int x = 10; Foo bar; MagicCollection objects; objects.add(x); objects.add(bar); for (const auto& obj : objects) { obj.dump(); }
  40. 40. 40 External polymorphism struct Dumpable { virtual void dump() const = 0; };
  41. 41. 41 External polymorphism struct Dumpable { virtual void dump() const = 0; }; template<typename T> class ConcreteDumpable<T> : public Dumpable { const T& value; public: ConcreteDumpable(const T& value) : value(value) {} virtual void dump() const override { ::dump(value); } };
  42. 42. 42 External polymorphism struct Dumpable { virtual void dump() const = 0; }; template<typename T> class ConcreteDumpable<T> : public Dumpable { const T& value; public: ConcreteDumpable(const T& value) : value(value) {} virtual void dump() const override { ::dump(value); } }; void dump(const Foo& foo) { foo.printToConsole(); }
  43. 43. 43 External polymorphism class Dumper { using DumpablePtr = std::unique_ptr<Dumpable>; std::vector<DumpablePtr> dumpables; public: template<typename T> void add(const T& obj) { dumpables.push_back( DumpablePtr(new ConcreteDumpable<T>(obj))); } void dumpAll() const { for (const auto& d : dumpables) { d->dump(); } } };
  44. 44. 44 External polymorphism class Dumper { using DumpablePtr = std::unique_ptr<Dumpable>; std::vector<DumpablePtr> dumpables; public: template<typename T> void add(const T& obj) { dumpables.push_back( DumpablePtr(new ConcreteDumpable<T>(obj))); } void dumpAll() const { for (const auto& d : dumpables) { d->dump(); } } } dumper; dumper.add(10); dumper.add(Foo()); dumper.dumpAll();
  45. 45. 45 External polymorphism  Симбиоз виртуального и статического полиморфизма  Для поддержки нового типа T надо добавить только ::dump(T)  Можно строить параллельные иерархии
  46. 46. 46 Новые возможности C++
  47. 47. 47 Новые возможности C++11: лямбды int x = 10; std::vector<int> v = { 1, 2, 3 }; for_each(v.begin(), v.end(), [x](int i){cout << i+x;});
  48. 48. 48 Новые возможности C++11: лямбды int x = 10; std::vector<int> v = { 1, 2, 3 }; for_each(v.begin(), v.end(), [x](int i){cout << i+x;}); class Lambda { int x; public: Lambda(int x) : x(x) {} void operator( )(int i) const {std::cout << i+x;} }; for_each(v.begin(), v.end(), Lambda());
  49. 49. 49 Новые возможности C++14: лямбды int x = 10; std::vector<int> v = { 1, 2, 3 }; for_each(v.begin(), v.end(), [x](auto i){cout << i+x;});
  50. 50. 50 Новые возможности C++14: лямбды int x = 10; std::vector<int> v = { 1, 2, 3 }; for_each(v.begin(), v.end(), [x](auto i){cout << i+x;}); class Lambda { int x; public: Lambda(int x) : x(x) {} template<typename T> void operator()(T i) const {std::cout << i+x;} };
  51. 51. 51 Новые возможности C++11: std::function void print(int i) { std::cout << i; } struct Print { void operator()(int i) { std::cout << i+x; } void print(int i) { std::cout << i+x; } } Print p; std::function<void(int)> p1 = print; std::function<void(int)> p2 = Print(); std::function<void(int)> p3 = std::bind(Print::print, p, _1); std::function<void(int)> p4 = std::bind(Print::print, &p, _1); std::function<void(const Print&, int)> p5 = &Print::print; std::function<void(int)> p6 = [](int i) { std::cout << i; };
  52. 52. 52 Новые возможности C++11: лямбды и std::function auto lambda1 = []() {}; auto lambda2 = []() {}; lambda1(); // fast std::function<void()> f = []() {}; f(); // slow template<typename Handler> void for_each(const std::vector<int>& v, const Handler& h); void for_each(const std::vector<int>& v, std::function<void(int)> f);
  53. 53. 53 Новые возможности C++11: std::function  Позволяют сохранить исполняемые объекты (включая лямбды), в том числе в коллекцию  Может быть медленной (~10 раз медленнее шаблонной функции)  Обеспечивает явную спецификацию интерфейса
  54. 54. 54 Пример из жизни
  55. 55. 55 Исходные условия id=1234 t time=2014.26.09 19:00 struct RecordBase { void Load(const std::string& str); std::string GetValue(const std::string& key) const; void SetValue(const std::string& key, const std::string& value); };  Хотим быстро создавать абстракции, позволяющие типизированно работать с записями
  56. 56. 56 Версия 1.0 struct EventRecord : RecordBase { int GetId() const { const auto& str = GetValue("id"); // Parse id from str return id; } void SetId(int id) { // Serialize id to str SetValue("id", str); } time_t GetTs() const { const auto& str = GetValue("time"); // Parse ts from str return ts; } void SetTs(time_t ts) { // Serialize ts to str SetValue("date", str); } };
  57. 57. 57 Версия 1.1 struct EventRecord : RecordBase { int GetId() const { return ::FromString(GetValue("id")); } void SetId(int id) { SetValue("id", ::ToString(id)); } int GetTs() const { return ::FromString(GetValue("time")); } void SetTs(time_t ts) { SetValue("time", ::ToString(ts)); } };
  58. 58. 58 Уходим из :: struct Serializer { template<typename T> static T FromString(const std::string& str) { return ::FromString<T>(str); } template<typename T> static string ToString(const T& value) { return ::ToString(value); } };
  59. 59. 59 Инкапсулируем логику RecordBase struct RecordBase { std::string GetValue(const std::string& key) const; void SetValue(const std::string& key, const std::string& value); template<typename T, typename Szr = Serializer> T Get(const string& key) const { return Szr::FromString<T>(GetValue(key)); } template<typename Szr = Serializer, typename T> void Set(const std::string& key, const T& value) { SetValue(key, Szr::ToString(ts)); } };
  60. 60. 60 Упрощаем EventRecord struct EventRecord : RecordBase { int GetId() const { return Get<int>("id"); } void SetId(int id) { Set<>("id", id); } int GetTs() const { return Get<time_t, DateSerializer>("time"); } void SetTs(time_t ts) { Set<DateSerializer>("time", ts); } };
  61. 61. 61 Еще больше инкапсуляции template<typename T, typename Szr = Serializer> class Property { RecordBase* record; std::string key; public: Property(RecordBase* record, const std::string& key) : record(record), key(key) {} T Get() const { return Szr::FromString(record->GetValue(key)); } void Set(const T& value) { record->SetValue(key, Szr::ToString(value)); } };
  62. 62. 62 Итоговая версия struct EventRecord : RecordBase { Property<int> Id; Property<time_t, DateSerializer> Ts; EventRecord() : Id(this, "id"), Ts(this, "time") {} }; EventRecord record; record.Id.Set(123); time_t ts = record.Ts.Get();
  63. 63. 63 Заключение
  64. 64. 64 Статический полиморфизм  Большая гибкость  Ограниченность в этапе компиляции  Поощряется свежими стандартами  Сложно писать библиотечный код  Просто писать клиентский код
  65. 65. 65 Спасибо за внимание! Дмитрий Леванов Ведущий разработчик Крипта levanov@yandex-team.ru
  66. 66. 66 Источники 1. http://www.inteks.ru/OOAD/P-Cources.OOAD.part1.pdf 2. http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern 3. http://www.cs.wustl.edu/~schmidt/PDF/External-Polymorphism.pdf 4. http://www.generic-programming.org/languages/cpp/techniques.php 5. http://eli.thegreenplace.net/2013/12/05/the-cost-of-dynamic-virtual- calls-vs-static-crtp-dispatch-in-c/

×