Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов, Яндекс
- 4. 4
Разработка Крипты
Много логов в разных форматах
Сложные цепочки обработки
Высокие требования к производительности
Много одинаковой похожей логики
Хочется делать всё однообразно
- 6. 6
Полиморфизм
Способ поставить в соответствие некой
грамматической конструкции контекстно-
зависимую семантику
или, по-русски:
Текст программы [почти] один и тот же, а
смысл разный
- 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
Виртуальный полиморфизм: минусы
Медленный вызов методов
Расход памяти на объекты
Требует наличия иерархии классов
Приходится иметь дело с T* или T&
Грабли с виртуальными методами
Инвариантность параметров
- 10. 10
Виртуальный полиморфизм: минусы
Медленный вызов методов
Расход памяти на объекты
Требует наличия иерархии классов
Приходится иметь дело с T* или T&
Грабли с виртуальными методами
Инвариантность параметров
Во многих случаях это не критично
- 11. 11
Виртуальный полиморфизм: минусы
Медленный вызов методов
Расход памяти на объекты
Требует наличия иерархии классов
Приходится иметь дело с T* или T&
Грабли с виртуальными методами
Инвариан��ность параметров
Во многих случаях это не критично
Но иногда может стать проблемой
- 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
То же самое, но лучше
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
То же самое, но лучше
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
То же самое, но лучше
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
То же самое, но лучше
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
- 21. 21
Статический полиморфизм: плюсы
Нет накладных расходов на вызов методов
Не надо наследоваться
Не надо иметь дело с указателями
Контрвариантность параметров
Можно использовать лямбды
- 22. 22
Статический полиморфизм: минусы
Нельзя положить в коллекцию
Сложно проверять правильность кода
Медленно компилируется
Может распухнуть бинарник
Нельзя явно задать интерфейсы
Мало помощи от IDE
- 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() {}
};
- 30. 30
«Виртуальный» вызов без virtual
a.k.a. Curiously Recurring Template Pattern
template<typename Derived>
class Game {
void end() {
static_cast<Derived*>(this)->end();
}
};
- 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
«Виртуальный» вызов без 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
- 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
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
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;
}
- 39. 39
Задача
Пишем инструмент для отладки
Есть множество объектов, не связанных
какой-либо иерархией
Хотим сложить их в одну коллекцию,
проитерироваться по ней, и распечатать
содержимое объектов
int x = 10;
Foo bar;
MagicCollection objects;
objects.add(x);
objects.add(bar);
for (const auto& obj : objects) {
obj.dump();
}
- 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
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
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
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
External polymorphism
Симбиоз виртуального и статического
полиморфизма
Для поддержки нового типа T надо добавить
только ::dump(T)
Можно строить параллельные иерархии
- 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
Новые возможности 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
Новые возможности 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
Новые возможности 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
Новые возможности 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
Новые возможности 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
Новые возможности C++11:
std::function
Позволяют сохранить исполняемые объекты
(включая лямбды), в том числе в коллекцию
Может быть медленной (~10 раз медленнее
шаблонной функции)
Обеспечивает явную спецификацию
интерфейса
- 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
Версия 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
Версия 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
Уходим из ::
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
Инкапсулируем логику 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
Упрощаем 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
Еще больше инкапсуляции
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
Итоговая версия
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();
- 64. 64
Статический полиморфизм
Большая гибкость
Ограниченность в этапе компиляции
Поощряется свежими стандартами
Сложно писать библиотечный код
Просто писать клиентский код