Статический и динамический полиморфизм в C++, Дмитрий Леванов
Некоторые паттерны реализации полиморфного поведения...
Transcript of Некоторые паттерны реализации полиморфного поведения...
1
2
Некоторые паттерны
реализации полиморфного
поведения в C++
Дмитрий Леванов
Ведущий разработчик Крипта
3
Крипта
4
Разработка Крипты
Много логов в разных форматах
Сложные цепочки обработки
Высокие требования к производительности
Много одинаковой похожей логики
Хочется делать всё однообразно
5
Полиморфизм
6
Полиморфизм
Способ поставить в соответствие некой
грамматической конструкции контекстно-
зависимую семантику
или, по-русски:
Текст программы [почти] один и тот же, а
смысл разный
7
Шаблоны проектирования
Abstract server/client
Adapter
Decorator
Proxy
Template method
Command
Strategy
…
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
Виртуальный полиморфизм: минусы
Медленный вызов методов
Расход памяти на объекты
Требует наличия иерархии классов Приходится иметь дело с T* или T&
Грабли с виртуальными методами
Инвариантность параметров
10
Виртуальный полиморфизм: минусы
Медленный вызов методов
Расход памяти на объекты
Требует наличия иерархии классов Приходится иметь дело с T* или T&
Грабли с виртуальными методами
Инвариантность параметров
Во многих случаях это не критично
11
Виртуальный полиморфизм: минусы
Медленный вызов методов
Расход памяти на объекты
Требует наличия иерархии классов Приходится иметь дело с T* или T&
Грабли с виртуальными методами
Инвариантность параметров
Во многих случаях это не критично
Но иногда может стать проблемой
12
Простой пример
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
То же самое, но лучше
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
То же самое, но лучше
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
То же самое, но лучше
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
То же самое, но лучше
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
Продолжаем улучшать
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
Совсем хорошо
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
Или так
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
Статический полиморфизм: плюсы
Нет накладных расходов на вызов методов
Не надо наследоваться
Не надо иметь дело с указателями
Контрвариантность параметров
Можно использовать лямбды
22
Статический полиморфизм: минусы
Нельзя положить в коллекцию
Сложно проверять правильность кода
Медленно компилируется
Может распухнуть бинарник
Нельзя явно задать интерфейсы
Мало помощи от IDE
23
Синтаксис иногда довольно
странный…
this->do();
24
Синтаксис иногда довольно
странный…
this->do();
typename T::iter f(typename T::iter i);
25
Синтаксис иногда довольно
странный…
this->do();
typename T::iter f(typename T::iter i);
this->template do<T>(); // WTF???
26
Curiously Recurring
Template Pattern
27
Template method
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
«Виртуальный» вызов без virtuala.k.a. Curiously Recurring Template Pattern
30
«Виртуальный» вызов без virtuala.k.a. Curiously Recurring Template Pattern
template<typename Derived>
class Game {
void end() {
static_cast<Derived*>(this)->end();
}
};
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*/}
};
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
33
CRTP
«Виртуальный» метод может быть
статическим
Может работать в ~7 раз быстрее виртуальной версии*
http://bit.ly/crtp_vs_virtual
34
Tag dispatching
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
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
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
External polymorphism
39
Задача
Пишем инструмент для отладки
Есть множество объектов, не связанных
какой-либо иерархией
Хотим сложить их в одну коллекцию,
проитерироваться по ней, и распечатать
содержимое объектов
int x = 10;
Foo bar;
MagicCollection objects;
objects.add(x);
objects.add(bar);
for (const auto& obj : objects) {
obj.dump();
}
40
External polymorphism
struct Dumpable {
virtual void dump() const = 0;
};
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
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
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(); }
}
};
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();
45
External polymorphism
Симбиоз виртуального и статического
полиморфизма Для поддержки нового типа T надо добавить
только ::dump(T)
Можно строить параллельные иерархии
46
Новые возможности 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;});
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
Новые возможности 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
Новые возможности 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
Новые возможности 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
Новые возможности 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
Новые возможности C++11: std::function
Позволяют сохранить исполняемые объекты
(включая лямбды), в том числе в коллекцию
Может быть медленной (~10 раз медленнее
шаблонной функции)
Обеспечивает явную спецификацию
интерфейса
54
Пример из жизни
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
Версия 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);
}
};
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
Уходим из ::
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
Инкапсулируем логику 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
Упрощаем 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
Еще больше инкапсуляции
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
Итоговая версия
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
Заключение
64
Статический полиморфизм
Большая гибкость
Ограниченность в этапе компиляции
Поощряется свежими стандартами
Сложно писать библиотечный код
Просто писать клиентский код
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/