Привет, Хабр!
Сегодня мы рассмотрим библиотеку Loki, которая названа в честь одного из самых интересных и хитроумных богов скандинавской мифологии. Как и его мифологический тёзка, Loki в C++ отличается гибкостью и изяществом, имея мощные инструменты для метапрограммирования и реализации паттернов проектирования.
Принципы Loki
-
Малое прекрасно:
-
Принцип заключается в минимизации внутренних зависимостей между компонентами библиотеки. Каждый компонент может быть использован независимо!
-
Например, существуют такие компоненты, как SmartPointer или TypeList, они могут быть использованы отдельно друг от друга.
-
-
Мультипликативное превосходство:
-
Loki фокусируется на достижении сложных, спец. дизайнов через комбинацию простых и абстрактных компонентов.
-
-
Минимальные зависимости:
-
Loki делает минимальные предположения о своем окружении и предоставляет гибкие точки расширения, чтобы можно было адаптировать библиотеку под нужды без необходимости вносить изменения в саму либу.
-
Основные компоненты Loki
Типы данных
TypeList — это метапрограммная структура данных, которая представляет собой список типов. Используют для манипуляций с типами на этапе компиляции.
Можно выполнять различные операции с TypeList: доступ к первому элементу, добавление и удаление элементов.
Например, доступ к первому элементу осуществляется с помощью front :
template <typename List> struct Front; template <typename Head, typename... Tail> struct Front<TypeList<Head, Tail...>> { using Type = Head; }; using FirstType = Front<MyTypes>::Type; // int
Добавление элемента в начало списка с помощью PushFront:
template <typename List, typename NewType> struct PushFront; template <typename... Types, typename NewType> struct PushFront<TypeList<Types...>, NewType> { using Type = TypeList<NewType, Types...>; }; using NewList = PushFront<MyTypes, char>::Type; // TypeList<char, int, double, std::string>
template <typename List, typename NewType> struct PushFront; template <typename... Types, typename NewType> struct PushFront<TypeList<Types...>, NewType> { using Type = TypeList<NewType, Types...>; }; using NewList = PushFront<MyTypes, char>::Type; // TypeList<char, int, double, std::string>
Еще существует NullType, который используется для обозначения конца списка типов или отсутствия типа.
struct NullType {}; using EmptyList = TypeList<>;
Умные указатели
Умные указатели в Loki представляют из себя шаблоны для управления ресурсами и автоматического освобождения памяти:
#include <loki/SmartPtr.h> using namespace Loki; typedef SmartPtr<int> IntPtr; void example() { IntPtr p(new int(10)); // автоматическое освобождение памяти при выходе из области видимости p }
Мультиметоды
Мультиметоды позволяют реализовать множественную диспетчеризацию в C++. Так можно вызывать различные функции в зависимости от типов аргументов во время выполнения:
#include <loki/MultiMethods.h> using namespace Loki; struct Base { virtual ~Base() {} }; struct Derived1 : Base {}; struct Derived2 : Base {}; void function(Derived1&) { /* ... */ } void function(Derived2&) { /* ... */ } int main() { Derived1 d1; Derived2 d2; Base* b1 = &d1; Base* b2 = &d2; // вызов функции в зависимости от типа аргумента Invoke(function, *b1); Invoke(function, *b2); }
Функторы
Функторы предоставляют объект-ориентированный способ работы с функциями:
#include <loki/Functor.h> using namespace Loki; void print(int x) { std::cout << x << std::endl; } int main() { Functor<void, LOKI_TYPELIST_1(int)> f(print); f(10); // Выведет 10 }
Паттерны проектирования: одиночка, фабрика, посетитель
Есть реализация паттерна одиночка с возможностью управления временем жизни объекта:
#include <loki/Singleton.h> using namespace Loki; class MySingleton { public: void DoSomething() { /* ... */ } }; int main() { MySingleton& instance = Singleton<MySingleton>::Instance(); instance.DoSomething(); }
Фабрика позволяет создавать объекты различных классов, не зная их точных типов:
#include <loki/Factory.h> using namespace Loki; class Base { public: virtual ~Base() {} virtual void DoSomething() = 0; }; class Derived : public Base { public: void DoSomething() override { std::cout << "Derived" << std::endl; } }; int main() { typedef Factory<AbstractFactoryUnit, Base, int> MyFactory; MyFactory factory; factory.Register<Derived>(1); std::unique_ptr<Base> obj(factory.CreateObject(1)); obj->DoSomething(); // Выведет "Derived" }
Посетитель позволяет добавлять новые операции к существующим объектам без изменения их классов:
#include <loki/Visitor.h> using namespace Loki; class Element { public: virtual void Accept(Visitor<void, Element>& visitor) = 0; }; class ConcreteElement : public Element { public: void Accept(Visitor<void, Element>& visitor) override { visitor.Visit(*this); } }; class ConcreteVisitor : public Visitor<void, Element> { public: void Visit(ConcreteElement& element) { std::cout << "Visited ConcreteElement" << std::endl; } }; int main() { ConcreteElement elem; ConcreteVisitor visitor; elem.Accept(visitor); // выведет "Visited ConcreteElement" }
Политика-ориентированный дизайн
Политика-ориентированный дизайн позволяет создавать классы, которые могут быть настроены с помощью шаблонных параметров. Достигается за счет разбиения поведения класса на несколько политик, каждая из которых реализует определенную функциональность:
template <typename T, typename ThreadingModel> class Singleton { public: static T& Instance() { static T instance; return instance; } private: Singleton() {} ~Singleton() {} }; class SingleThreaded { public: typedef int Lock; }; class MultiThreaded { public: class Lock { public: Lock() { /* lock mechanism */ } ~Lock() { /* unlock mechanism */ } }; }; typedef Singleton<MyClass, SingleThreaded> MySingleton;
Примеры использования
TypeList для динамического создания объектов:
#include <iostream> #include <loki/Typelist.h> #include <loki/Factory.h> using namespace Loki; // определяем несколько классов class A { public: void Display() { std::cout << "Class A\n"; } }; class B { public: void Display() { std::cout << "Class B\n"; } }; class C { public: void Display() { std::cout << "Class C\n"; } }; // создаем TypeList из классов typedef TYPELIST_3(A, B, C) MyTypeList; // фабрика для создания объектов из TypeList typedef Factory<AbstractFactoryUnit, MyTypeList> MyFactory; int main() { MyFactory factory; factory.Register<A>(1); factory.Register<B>(2); factory.Register<C>(3); std::unique_ptr<A> a(factory.CreateObject(1)); std::unique_ptr<B> b(factory.CreateObject(2)); std::unique_ptr<C> c(factory.CreateObject(3)); a->Display(); b->Display(); c->Display(); return 0; }
Так можно динамически создавать объекты различных типов, используя TypeList и фабрику.
Управление памятью с помощью SmartPtr:
#include <iostream> #include <loki/SmartPtr.h> using namespace Loki; class Resource { public: Resource() { std::cout << "Resource Acquired\n"; } ~Resource() { std::cout << "Resource Released\n"; } }; void useResource() { SmartPtr<Resource> res(new Resource()); // ресурс автоматом освободится в конце блока } int main() { useResource(); return 0; }
Реализация многометодного диспетчера:
#include <iostream> #include <loki/MultiMethods.h> using namespace Loki; class Animal { public: virtual ~Animal() {} LOKI_DEFINE_VISITABLE() }; class Dog : public Animal { public: void Bark() { std::cout << "Woof!\n"; } LOKI_DEFINE_VISITABLE() }; class Cat : public Animal { public: void Meow() { std::cout << "Meow!\n"; } LOKI_DEFINE_VISITABLE() }; void Speak(Animal&) { std::cout << "Unknown animal sound\n"; } void Speak(Dog& d) { d.Bark(); } void Speak(Cat& c) { c.Meow(); } int main() { Dog dog; Cat cat; Animal* animals[] = { &dog, &cat }; for (Animal* animal : animals) { Invoke(Speak, *animal); } return 0; }
Functor для обратных вызовов
#include <iostream> #include <loki/Functor.h> using namespace Loki; void PrintNumber(int number) { std::cout << "Number: " << number << std::endl; } int main() { Functor<void, LOKI_TYPELIST_1(int)> func(PrintNumber); func(42); return 0; }
Подробнее с библиотекой можно ознакомиться здесь.
В завершение хочу рассказать об открытых уроках для разработчиков на C++, которые совсем скоро пройдут в Otus:
-
11 июня: Условные переменные в С++. Узнаете, что такое std::condition_variable, какие задачи он решает и типовые ошибки при его использовании. Узнаете, что такое spurious wakeup и напишите несколько concurrency-примитивов на основе condition_variable. Записаться бесплатно можно по ссылке.
-
24 июня: Как разработчику на С++ организовать кроссплатформенную разработку? На этом уроке узнаете, как решить проблему поиска зависимостей, напишите conan-файл и сможете организовать свой сервер пакетов в своей экосистеме CI/CD. Запись по ссылке.
ссылка на оригинал статьи https://habr.com/ru/articles/819661/
Добавить комментарий