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

29e169dc2fd762e20441a098703189ed.jpg

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

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

Принципы Loki

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

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

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

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

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

    • Loki делает минимальные предположения о своем окружении и предоставляет гибкие точки расширения, чтобы можно было адаптировать библиотеку под нужды без необходимости вносить изменения в саму либу.

Основные компоненты Loki

Типы данных

TypeList — это метапрограммная структура данных, которая представляет собой список типов. Используют для манипуляций с типами на этапе компиляции.

Можно выполнять различные операции с TypeList: доступ к первому элементу, добавление и удаление элементов.

Например, доступ к первому элементу осуществляется с помощью front:

template 
struct Front;

template 
struct Front> {
    using Type = Head;
};

using FirstType = Front::Type;  // int

Добавление элемента в начало списка с помощью PushFront:

template 
struct PushFront;

template 
struct PushFront, NewType> {
    using Type = TypeList;
};

using NewList = PushFront::Type;  // TypeList
template 
struct PushFront;

template 
struct PushFront, NewType> {
    using Type = TypeList;
};

using NewList = PushFront::Type;  // TypeList

Еще существует NullType, который используется для обозначения конца списка типов или отсутствия типа.

struct NullType {};

using EmptyList = TypeList<>;

Умные указатели

Умные указатели в Loki представляют из себя шаблоны для управления ресурсами и автоматического освобождения памяти:

#include 

using namespace Loki;

typedef SmartPtr IntPtr;

void example() {
    IntPtr p(new int(10));
    // автоматическое освобождение памяти при выходе из области видимости p
}

Мультиметоды

Мультиметоды позволяют реализовать множественную диспетчеризацию в C++. Так можно вызывать различные функции в зависимости от типов аргументов во время выполнения:

#include 

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 

using namespace Loki;

void print(int x) {
    std::cout << x << std::endl;
}

int main() {
    Functor f(print);
    f(10);  // Выведет 10
}

Паттерны проектирования: одиночка, фабрика, посетитель

Есть реализация паттерна одиночка с возможностью управления временем жизни объекта:

#include 

using namespace Loki;

class MySingleton {
public:
    void DoSomething() { /* ... */ }
};

int main() {
    MySingleton& instance = Singleton::Instance();
    instance.DoSomething();
}

Фабрика позволяет создавать объекты различных классов, не зная их точных типов:

#include 

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 MyFactory;
    MyFactory factory;
    factory.Register(1);
    std::unique_ptr obj(factory.CreateObject(1));
    obj->DoSomething();  // Выведет "Derived"
}

Посетитель позволяет добавлять новые операции к существующим объектам без изменения их классов:

#include 

using namespace Loki;

class Element {
public:
    virtual void Accept(Visitor& visitor) = 0;
};

class ConcreteElement : public Element {
public:
    void Accept(Visitor& visitor) override {
        visitor.Visit(*this);
    }
};

class ConcreteVisitor : public Visitor {
public:
    void Visit(ConcreteElement& element) {
        std::cout << "Visited ConcreteElement" << std::endl;
    }
};

int main() {
    ConcreteElement elem;
    ConcreteVisitor visitor;
    elem.Accept(visitor);  // выведет "Visited ConcreteElement"
}

Политика-ориентированный дизайн

Политика-ориентированный дизайн позволяет создавать классы, которые могут быть настроены с помощью шаблонных параметров. Достигается за счет разбиения поведения класса на несколько политик, каждая из которых реализует определенную функциональность:

template 
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 MySingleton;

Примеры использования

TypeList для динамического создания объектов:

#include 
#include 
#include 

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 MyFactory;

int main() {
    MyFactory factory;
    factory.Register(1);
    factory.Register(2);
    factory.Register(3);

    std::unique_ptr a(factory.CreateObject(1));
    std::unique_ptr b(factory.CreateObject(2));
    std::unique_ptr c(factory.CreateObject(3));

    a->Display();
    b->Display();
    c->Display();

    return 0;
}

Так можно динамически создавать объекты различных типов, используя TypeList и фабрику.

Управление памятью с помощью SmartPtr:

#include 
#include 

using namespace Loki;

class Resource {
public:
    Resource() { std::cout << "Resource Acquired\n"; }
    ~Resource() { std::cout << "Resource Released\n"; }
};

void useResource() {
    SmartPtr res(new Resource());
    // ресурс автоматом освободится в конце блока
}

int main() {
    useResource();
    return 0;
}

Реализация многометодного диспетчера:

#include 
#include 

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 
#include 

using namespace Loki;

void PrintNumber(int number) {
    std::cout << "Number: " << number << std::endl;
}

int main() {
    Functor func(PrintNumber);
    func(42);
    return 0;
}

Подробнее с библиотекой можно ознакомиться здесь.

В завершение хочу рассказать об открытых уроках для разработчиков на C++, которые совсем скоро пройдут в Otus:

  • 11 июня: Условные переменные в С++. Узнаете, что такое std: condition_variable, какие задачи он решает и типовые ошибки при его использовании. Узнаете, что такое spurious wakeup и напишите несколько concurrency-примитивов на основе condition_variable. Записаться бесплатно можно по ссылке.

  • 24 июня: Как разработчику на С++ организовать кроссплатформенную разработку? На этом уроке узнаете, как решить проблему поиска зависимостей, напишите conan-файл и сможете организовать свой сервер пакетов в своей экосистеме CI/CD. Запись по ссылке.

Habrahabr.ru прочитано 10830 раз