Русский
Русский
English
Статистика
Реклама

Dynamic_cast на этапе компиляции

Приветствую все читающих :)


О чём статья (или задача статьи): практический ответ на вопрос "возможно ли создать большой проект так, чтобы полностью отказаться от dynamic_cast на этапе выполнения?", где под большим проектом подразумевает такой в котором уже нет человека, что бы держал в голове всю кодовую базу проекта целиком.
Предварительный ответ: ДА, это возможно возможно создать механизм, позволяющий решить задачу dynamic_cast на этапе компиляции, но едва ли подобное будет применяться на практике по причинам как: (1) с самого начала целевой проект должен строиться согласно наперёд заданным правилам, в следствии чего взять и применить методику к существующему проекту, очень трудоёмко (2) существенное повышение сложности кода с точки зрения удобства его читаемости в определённых местах, где, собственно, происходит замена логики dynamic_cast на предложенную ниже (3) использование шаблонов, что может быть неприемлемым в некоторых проектах по идеологическим соображениям ответственных за него (4) интерес автора исключительно в том, чтобы дать ответ на поставленный вопрос, а не в том, чтобы создать универсальный и удобный механизм решения поставленной задачи (в конце-концов, не нужно на практике решать проблемы, которые не являются насущными).


Идея реализации


За основу была взята идея списка типов, описанная Андреем Александреску (https://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80%D0%B5%D1%81%D0%BA%D1%83,_%D0%90%D0%BD%D0%B4%D1%80%D0%B5%D0%B9) и реализованная им же в библиотеке Loki (http://loki-lib.sourceforge.net/html/a00681.html). Данная идея была доработана по следующим пунктам (пункты помеченные * означают, что по данному пункту автор статьи не согласен с видением Александреску по поводу списков типов):


  • добавлена возможность генерации произвольного по длине списка типов без использования макросов и/или шаблонных структур, с количеством шаблонных параметров равных длине создаваемого списка;
  • добавлена возможность генерации списка типов на основе типа(ов) и/или существующего списка(ов) типов в их произвольной комбинации;
  • *удалена возможность создавать списки типов элементы которых могут являться списками типов;
  • *удалены функции MostDerived и DerivedToFront как и любая логика завязанная на наследовании классов, т.к. (1) логика её работы сильно зависит от порядка типов в списке типов, а потому требует бдительности от программиста при создании соответствующих списков и, что более важно, полного знания проекта программистом, который будет применять эту логику, что противоречит условиям задачи (2) распознавание наследования вниз по иерархии наследования, вообще говоря, простая задача не требующая какой-либо дополнительной логики этапа компиляции помимо уже имеющейся, тогда как автора статьи интересует в первую очередь логика распознавания наследования вверх на этапе компиляции, в чём выше обозначенные функции помочь не в силах;
  • добавлены проверки через static_assert, что позволяет получать осмысленные сообщения об ошибках при компиляции списка типов, в случае возникновения таковых;
  • добавлены функции как RemoveFromSize, CutFromSize позволяющие получать подсписки списков типов.
    Итоговый код библиотеки, для работы со списками типов, в полном виде вы можете посмотреть здесь (https://github.com/AlexeyPerestoronin/Cpp_TypesList), тогда как в статье будет присутствовать код, непосредственно использующий функционал данной библиотеки, необходимый для решения задачи.

Для начала будет представлен весь тестовый код с демонстрацией решения поставленной задачи, а далее, один за одним будут представлены и объяснены фрагменты из этого кода, последовательное ознакомление с которыми должно прояснить задумку автора.


#include <gtest/gtest.h>#include <TypesList.hpp>#include <memory>class A {    public:    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<void>>;    A() {}    A(int a) {        buffer << ' ' << a;    }    virtual void F1() = 0;    protected:    std::stringstream buffer;};class B : public A {    public:    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<A, A::BASE_t>>;    B() {}    B(int a, int b)        : A(a) {        buffer << ' ' << b;    }    virtual void F1() override {        std::cout << "class::B" << buffer.str() << std::endl;    }};class C : public B {    public:    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<B, B::BASE_t>>;    C() {}    C(int a, int b, int c)        : B(a, b) {        buffer << ' ' << c;    }    virtual void F1() override {        std::cout << "class::C" << buffer.str() << std::endl;    }};class D : public C {    public:    using BASE_t = TL::Refine_R<TL::CreateTypesList_R<C, C::BASE_t>>;    D() {}    D(int a, int b, int c, int d)        : C(a, b, c) {        buffer << ' ' << d;    }    virtual void F1() override {        std::cout << "class::D" << buffer.str() << std::endl;    }};TEST(Check_class_bases, test) {    {        using TClass = A;        EXPECT_EQ(TClass::BASE_t::size, 1);        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));    }    {        using TClass = B;        EXPECT_EQ(TClass::BASE_t::size, 2);        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));    }    {        using TClass = C;        EXPECT_EQ(TClass::BASE_t::size, 3);        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, B>));    }    {        using TClass = D;        EXPECT_EQ(TClass::BASE_t::size, 4);        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, B>));        EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, C>));    }}// TT - Type to Typetemplate<class Type, class BASE_t>struct T2T {    std::shared_ptr<Type> value;    using PossibleTo_t = BASE_t;};template<class To, class From, class... Arguments>auto T2TMake(Arguments&&... arguments) {    T2T<To, TL::Refine_R<TL::CreateTypesList_R<From, From::BASE_t>>> result{};    result.value = std::make_shared<From>(arguments...);    return result;}template<class BASE_t>void AttemptUse(T2T<A, BASE_t> tb) {    static_assert(TL::IsInList_R<BASE_t, C>, "this function can to use only with C-derivative params");    tb.value->F1();}TEST(T2TMake, test) {    {        auto value = T2TMake<A, B>();        using TClass = decltype(value)::PossibleTo_t;        EXPECT_EQ(TClass::size, 3);        EXPECT_TRUE((TL::IsInList_R<TClass, void>));        EXPECT_TRUE((TL::IsInList_R<TClass, A>));        EXPECT_TRUE((TL::IsInList_R<TClass, B>));        // AttemptUse(value); // compilation error    }    {        auto value = T2TMake<A, B>(1, 2);        using TClass = decltype(value)::PossibleTo_t;        EXPECT_EQ(TClass::size, 3);        EXPECT_TRUE((TL::IsInList_R<TClass, void>));        EXPECT_TRUE((TL::IsInList_R<TClass, A>));        EXPECT_TRUE((TL::IsInList_R<TClass, B>));        // AttemptUse(value); // compilation error    }    {        auto value = T2TMake<A, C>();        using TClass = decltype(value)::PossibleTo_t;        EXPECT_EQ(TClass::size, 4);        EXPECT_TRUE((TL::IsInList_R<TClass, void>));        EXPECT_TRUE((TL::IsInList_R<TClass, A>));        EXPECT_TRUE((TL::IsInList_R<TClass, B>));        EXPECT_TRUE((TL::IsInList_R<TClass, C>));        AttemptUse(value);    }    {        auto value = T2TMake<A, C>(1, 2, 3);        using TClass = decltype(value)::PossibleTo_t;        EXPECT_EQ(TClass::size, 4);        EXPECT_TRUE((TL::IsInList_R<TClass, void>));        EXPECT_TRUE((TL::IsInList_R<TClass, A>));        EXPECT_TRUE((TL::IsInList_R<TClass, B>));        EXPECT_TRUE((TL::IsInList_R<TClass, C>));        AttemptUse(value);    }    {        auto value = T2TMake<A, D>();        using TClass = decltype(value)::PossibleTo_t;        EXPECT_EQ(TClass::size, 5);        EXPECT_TRUE((TL::IsInList_R<TClass, void>));        EXPECT_TRUE((TL::IsInList_R<TClass, A>));        EXPECT_TRUE((TL::IsInList_R<TClass, B>));        EXPECT_TRUE((TL::IsInList_R<TClass, C>));        EXPECT_TRUE((TL::IsInList_R<TClass, D>));        AttemptUse(value);    }    {        auto value = T2TMake<A, D>(1, 2, 3, 4);        using TClass = decltype(value)::PossibleTo_t;        EXPECT_EQ(TClass::size, 5);        EXPECT_TRUE((TL::IsInList_R<TClass, void>));        EXPECT_TRUE((TL::IsInList_R<TClass, A>));        EXPECT_TRUE((TL::IsInList_R<TClass, B>));        EXPECT_TRUE((TL::IsInList_R<TClass, C>));        EXPECT_TRUE((TL::IsInList_R<TClass, D>));        AttemptUse(value);    }}int main(int argc, char* argv[]) {    ::testing::InitGoogleTest(&argc, argv);    return RUN_ALL_TESTS();}

  1. Создание первого класса в иерархии class A


    class A {public:using BASE_t = TL::Refine_R<TL::CreateTypesList_R<void>>;A() {}A(int a) {    buffer << ' ' << a;}virtual void F1() = 0;protected:std::stringstream buffer;};
    

    class A это простой чисто-вирутуальный класс, главной особенностью которого является строка: using BASE_t = TL::Refine_R<TL::CreateTypesList_R<void>>;, которая определяет для класса список типов от которых наследуется класс.
    Здесь и далее:


    • TL::CreateTypesList_R структура, создающая список типов произвольной длинны.
    • TL::Refine_R структура, очищающая список типов от дубликатов, в случае наличия таковых.
      Т.к. класс А ни от кого не наследуется, то единственным типом к которому он может быть напрямую приведён является void.

  2. Создание втрого класса в иерархии class B


    class B : public A {public:using BASE_t = TL::Refine_R<TL::CreateTypesList_R<A, A::BASE_t>>;B() {}B(int a, int b)    : A(a) {    buffer << ' ' << b;}virtual void F1() override {    std::cout << "class::B" << buffer.str() << std::endl;}};
    

    Как видим, класс В наследуется от класса А, а потому в его BASE_t списке типов к которым может быть приведёт класс В, содержится класс А и все базовые типы класса А.


  3. Создание третьего класса в иерархии class C


    class C : public B {public:using BASE_t = TL::Refine_R<TL::CreateTypesList_R<B, B::BASE_t>>;C() {}C(int a, int b, int c)    : B(a, b) {    buffer << ' ' << c;}virtual void F1() override {    std::cout << "class::C" << buffer.str() << std::endl;}};
    

    Класс С, является наследником класса В, следовательно в его BASE_t содержится класс В, и все базовые типы класса В.


  4. Создание четвёртого класса в иерархии class D


    class D : public C {public:using BASE_t = TL::Refine_R<TL::CreateTypesList_R<C, C::BASE_t>>;D() {}D(int a, int b, int c, int d)    : C(a, b, c) {    buffer << ' ' << d;}virtual void F1() override {    std::cout << "class::D" << buffer.str() << std::endl;}};
    

    Аналогично предыдущему классу, класс D наследуется от класса С и его BASE_t содержит класс С и все его базовые типы.


  5. Проверка базовых типов классов
    Здесь и далее, структура TL::IsInList_R<TypesList, Type> возвращает true когда и только тогда, когда тип Type входит в список типов TypesList, и false в противном случае.
    TEST(Check_class_bases, test) {{    using TClass = A;    EXPECT_EQ(TClass::BASE_t::size, 1);    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));}{    using TClass = B;    EXPECT_EQ(TClass::BASE_t::size, 2);    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));}{    using TClass = C;    EXPECT_EQ(TClass::BASE_t::size, 3);    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, B>));}{    using TClass = D;    EXPECT_EQ(TClass::BASE_t::size, 4);    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, void>));    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, A>));    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, B>));    EXPECT_TRUE((TL::IsInList_R<TClass::BASE_t, C>));}}
    

    Как видно из фрагмента кода, каждый из созданных классов: class A, class B, class C и class D, содержит в своём BASE_t типы к которым этот класс может быть приведён вниз по иерархии наследования.


    Создание структуры с информацией о наследовании вверх по иерархии
    // T2T - Type to Typetemplate<class Type, class BASE_t>struct T2T {std::shared_ptr<Type> value;using PossibleTo_t = BASE_t;};
    

    Данная структура предназначена для хранения указателя на экземпляр value типа Type и списка типов PossibleTo_t к которым value может быть приведён, включая типы выше по иерархии от (унаследованные от) Type.


    Создание функции для создания структуры T2T
    template<class To, class From, class... Arguments>auto T2TMake(Arguments&&... arguments) {T2T<To, TL::Refine_R<TL::CreateTypesList_R<From, From::BASE_t>>> result{};result.value = std::make_shared<From>(arguments...);return result;}
    

    Шаблонные параметры функции T2TMake имеют следующее предназначение:

    • From истинный тип объекта, создаваемого для хранения в структуре T2T;
    • To тип под которым созданный объект хранится в структуре T2T;
    • Arguments типы аргументов для создания целевого объекта.
      Как видно, данная фукнция будет компилироваться только в случае, если тип From является наследником типа To, а запись TL::Refine_R<TL::CreateTypesList_R<From, From::BASE_t>> создаёт список типов для структуры T2T по которому в дальнейшем можно будeт однозначно определить всё множество типов к которым может быть приведён указатель на объект value.

    Создание функции для проверки корректности работы структуры T2T
    template<class BASE_t>void AttemptUse(T2T<A, BASE_t> tb) {static_assert(TL::IsInList_R<BASE_t, C>, "this function can to use only with C-derivative params");tb.value->F1();}
    

    По придуманным условиям, данная функция может работать с объектами класса А, являющимися приведёнными от объектов не ниже класса С по иерархии типов, причём, и это самое важное, данное условие проверяется ещё на этапе компиляции.


    Итоговое тестирование созданной логики
    TEST(T2TMake, test) {{    auto value = T2TMake<A, B>();    using TClass = decltype(value)::PossibleTo_t;    EXPECT_EQ(TClass::size, 3);    EXPECT_TRUE((TL::IsInList_R<TClass, void>));    EXPECT_TRUE((TL::IsInList_R<TClass, A>));    EXPECT_TRUE((TL::IsInList_R<TClass, B>));    // AttemptUse(value); // compilation error}{    auto value = T2TMake<A, B>(1, 2);    using TClass = decltype(value)::PossibleTo_t;    EXPECT_EQ(TClass::size, 3);    EXPECT_TRUE((TL::IsInList_R<TClass, void>));    EXPECT_TRUE((TL::IsInList_R<TClass, A>));    EXPECT_TRUE((TL::IsInList_R<TClass, B>));    // AttemptUse(value); // compilation error}{    auto value = T2TMake<A, C>();    using TClass = decltype(value)::PossibleTo_t;    EXPECT_EQ(TClass::size, 4);    EXPECT_TRUE((TL::IsInList_R<TClass, void>));    EXPECT_TRUE((TL::IsInList_R<TClass, A>));    EXPECT_TRUE((TL::IsInList_R<TClass, B>));    EXPECT_TRUE((TL::IsInList_R<TClass, C>));    AttemptUse(value);}{    auto value = T2TMake<A, C>(1, 2, 3);    using TClass = decltype(value)::PossibleTo_t;    EXPECT_EQ(TClass::size, 4);    EXPECT_TRUE((TL::IsInList_R<TClass, void>));    EXPECT_TRUE((TL::IsInList_R<TClass, A>));    EXPECT_TRUE((TL::IsInList_R<TClass, B>));    EXPECT_TRUE((TL::IsInList_R<TClass, C>));    AttemptUse(value);}{    auto value = T2TMake<A, D>();    using TClass = decltype(value)::PossibleTo_t;    EXPECT_EQ(TClass::size, 5);    EXPECT_TRUE((TL::IsInList_R<TClass, void>));    EXPECT_TRUE((TL::IsInList_R<TClass, A>));    EXPECT_TRUE((TL::IsInList_R<TClass, B>));    EXPECT_TRUE((TL::IsInList_R<TClass, C>));    EXPECT_TRUE((TL::IsInList_R<TClass, D>));    AttemptUse(value);}{    auto value = T2TMake<A, D>(1, 2, 3, 4);    using TClass = decltype(value)::PossibleTo_t;    EXPECT_EQ(TClass::size, 5);    EXPECT_TRUE((TL::IsInList_R<TClass, void>));    EXPECT_TRUE((TL::IsInList_R<TClass, A>));    EXPECT_TRUE((TL::IsInList_R<TClass, B>));    EXPECT_TRUE((TL::IsInList_R<TClass, C>));    EXPECT_TRUE((TL::IsInList_R<TClass, D>));    AttemptUse(value);}}
    


    Выводы


    dynamic_cast на этапе компиляции это реально.
    Однако, вопрос о целесообразности остаётся насущным.


    Спасибо всем, кто прочитал статью :) буду рад узнать ваш опыт, мнение по, или, возможно, даже решение описанной в статье задачи в комментариях.

Источник: habr.com
К списку статей
Опубликовано: 24.12.2020 18:19:54
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

C++

C++11

Templates

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru