Основываясь на опыте разработки Крипты, Дмитрий рассмотрит средства реализации статического и динамического полиморфизма в 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&
Грабли с виртуальными методами
Инвариантность параметров
Во многих случаях это не критично
Но иногда может стать проблемой
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
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
Или так
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
Статический полиморфизм: плюсы
Нет накладных расходов на вызов методов
Не надо наследоваться
Не надо иметь дело с указателями
Контрвариантность параметров
Можно использовать лямбды
22. 22
Статический полиморфизм: минусы
Нельзя положить в коллекцию
Сложно проверять правильность кода
Медленно компилируется
Может распухнуть бинарник
Нельзя явно задать интерфейсы
Мало помощи от IDE
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
33. 33
CRTP
«Виртуальный» метод может быть
статическим
Может работать в ~7 раз быстрее
виртуальной версии*
⃰ http://bit.ly/crtp_vs_virtual
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();
}
45. 45
External polymorphism
Симбиоз виртуального и статического
полиморфизма
Для поддержки нового типа T надо добавить
только ::dump(T)
Можно строить параллельные иерархии
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 раз медленнее
шаблонной функции)
Обеспечивает явную спецификацию
интерфейса
64. 64
Статический полиморфизм
Большая гибкость
Ограниченность в этапе компиляции
Поощряется свежими стандартами
Сложно писать библиотечный код
Просто писать клиентский код