Некоторые паттерны реализации полиморфного поведения...

66
1

Transcript of Некоторые паттерны реализации полиморфного поведения...

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

1

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

2

Некоторые паттерны

реализации полиморфного

поведения в C++

Дмитрий Леванов

Ведущий разработчик Крипта

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

3

Крипта

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

4

Разработка Крипты

Много логов в разных форматах

Сложные цепочки обработки

Высокие требования к производительности

Много одинаковой похожей логики

Хочется делать всё однообразно

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

5

Полиморфизм

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

6

Полиморфизм

Способ поставить в соответствие некой

грамматической конструкции контекстно-

зависимую семантику

или, по-русски:

Текст программы [почти] один и тот же, а

смысл разный

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

7

Шаблоны проектирования

Abstract server/client

Adapter

Decorator

Proxy

Template method

Command

Strategy

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

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

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

9

Виртуальный полиморфизм: минусы

Медленный вызов методов

Расход памяти на объекты

Требует наличия иерархии классов Приходится иметь дело с T* или T&

Грабли с виртуальными методами

Инвариантность параметров

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

10

Виртуальный полиморфизм: минусы

Медленный вызов методов

Расход памяти на объекты

Требует наличия иерархии классов Приходится иметь дело с T* или T&

Грабли с виртуальными методами

Инвариантность параметров

Во многих случаях это не критично

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

11

Виртуальный полиморфизм: минусы

Медленный вызов методов

Расход памяти на объекты

Требует наличия иерархии классов Приходится иметь дело с T* или T&

Грабли с виртуальными методами

Инвариантность параметров

Во многих случаях это не критично

Но иногда может стать проблемой

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

12

Простой пример

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

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);

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

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);

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

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);

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

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 тактов на вызов)

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

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

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

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);

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

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; });

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

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);

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

21

Статический полиморфизм: плюсы

Нет накладных расходов на вызов методов

Не надо наследоваться

Не надо иметь дело с указателями

Контрвариантность параметров

Можно использовать лямбды

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

22

Статический полиморфизм: минусы

Нельзя положить в коллекцию

Сложно проверять правильность кода

Медленно компилируется

Может распухнуть бинарник

Нельзя явно задать интерфейсы

Мало помощи от IDE

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

23

Синтаксис иногда довольно

странный…

this->do();

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

24

Синтаксис иногда довольно

странный…

this->do();

typename T::iter f(typename T::iter i);

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

25

Синтаксис иногда довольно

странный…

this->do();

typename T::iter f(typename T::iter i);

this->template do<T>(); // WTF???

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

26

Curiously Recurring

Template Pattern

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

27

Template method

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

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() {}

};

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

29

«Виртуальный» вызов без virtuala.k.a. Curiously Recurring Template Pattern

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

30

«Виртуальный» вызов без virtuala.k.a. Curiously Recurring Template Pattern

template<typename Derived>

class Game {

void end() {

static_cast<Derived*>(this)->end();

}

};

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

31

«Виртуальный» вызов без virtuala.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*/}

};

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

32

«Виртуальный» вызов без virtuala.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

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

33

CRTP

«Виртуальный» метод может быть

статическим

Может работать в ~7 раз быстрее виртуальной версии*

http://bit.ly/crtp_vs_virtual

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

34

Tag dispatching

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

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;

}

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

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;

}

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

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;

}

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

38

External polymorphism

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

39

Задача

Пишем инструмент для отладки

Есть множество объектов, не связанных

какой-либо иерархией

Хотим сложить их в одну коллекцию,

проитерироваться по ней, и распечатать

содержимое объектов

int x = 10;

Foo bar;

MagicCollection objects;

objects.add(x);

objects.add(bar);

for (const auto& obj : objects) {

obj.dump();

}

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

40

External polymorphism

struct Dumpable {

virtual void dump() const = 0;

};

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

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);

}

};

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

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();

}

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

43

External polymorphismclass 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(); }

}

};

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

44

External polymorphismclass 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();

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

45

External polymorphism

Симбиоз виртуального и статического

полиморфизма Для поддержки нового типа T надо добавить

только ::dump(T)

Можно строить параллельные иерархии

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

46

Новые возможности C++

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

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;});

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

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());

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

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;});

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

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;}

};

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

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; };

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

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);

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

53

Новые возможности C++11: std::function

Позволяют сохранить исполняемые объекты

(включая лямбды), в том числе в коллекцию

Может быть медленной (~10 раз медленнее

шаблонной функции)

Обеспечивает явную спецификацию

интерфейса

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

54

Пример из жизни

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

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);

};

Хотим быстро создавать абстракции, позволяющие

типизированно работать с записями

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

56

Версия 1.0struct 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);

}

};

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

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));

}

};

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

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);

}

};

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

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));

}

};

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

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);

}

};

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

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));

}

};

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

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();

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

63

Заключение

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

64

Статический полиморфизм

Большая гибкость

Ограниченность в этапе компиляции

Поощряется свежими стандартами

Сложно писать библиотечный код

Просто писать клиентский код

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

65

Спасибо за внимание!

Дмитрий Леванов

Ведущий разработчик Крипта

[email protected]

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

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/