Локи в C++: библиотека для проектирования и метапрограммирования

от автора

Привет, Хабр!

Сегодня мы рассмотрим библиотеку Loki, которая названа в честь одного из самых интересных и хитроумных богов скандинавской мифологии. Как и его мифологический тёзка, Loki в C++ отличается гибкостью и изяществом, имея мощные инструменты для метапрограммирования и реализации паттернов проектирования.

Принципы Loki

  1. Малое прекрасно:

    • Принцип заключается в минимизации внутренних зависимостей между компонентами библиотеки. Каждый компонент может быть использован независимо!

    • Например, существуют такие компоненты, как SmartPointer или TypeList, они могут быть использованы отдельно друг от друга.

  2. Мультипликативное превосходство:

    • Loki фокусируется на достижении сложных, спец. дизайнов через комбинацию простых и абстрактных компонентов.

  3. Минимальные зависимости:

    • 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *