Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > C/C++: Прочее > Автоматизация для C++-программистов


Автор: Flex Ferrum 12.05.17, 11:27
Пока Цайне Куль, он же Никонов, он же Пархоменко рассыпается в многошуме на тему супер-пупер-языков и всего такого прочего, поговорим здесь про более практичные вещи.
Если кто не в курсе, в этом апреле я ездил на конференцию ACCU с докладом, посвящённым тому, как с использованием clang'а можно написать утилиту, которая превращает исходный код в исходный код. (Как только организаторы конференции выложат доклады в открытый доступ - дам на него ссылку. Ну, или идите в мой about в клубе - там есть). Но доклад, это хорошо, а пример реализации такой утилиты - ещё лучше. И такой пример есть у меня! :) Лежит здесь: flex_lib/codegen

Что это, собственно, такое. Это утилита, которая собирается вместе с инфраструктурай кланга (то есть включает в себя clang compiler frontend). Ей передаётся на вход файл с исходным текстом и тип того, что нужно сгенерировать. Она пропускает файл через компилятор, получает AST, и на базе этого AST генерирует новый код, содержащий реализацию требуемого функционала. В настоящий момент утилита умеет:

- Генерировать функции конвертации enum'ов в строки и обратно.
- Генерировать стандартную реализацию прокси-методов в соответствии с pimpl-идиомой.

Планируется всё это дорабатывать и расширять, в частности:
- Добавить генерацию boost-сериализаторов для структур и классов.
- Добавить ORM-мэппинг для, скажем, SQLite'а.
- Генерация mock-классов для Google Mock.
- Чего-нибудь ещё полезного.

Да-да. В C++ до сих пор нет рефлекшена и метаклассов. Приходится извращаться. :)

Автор: Flex Ferrum 17.05.17, 13:38
Генерация pimpl-вызовов допилена и выложена в репозиторий. Собственно, о чём это. Предполагаю, что с идиомой pimpl знакомы если не все, то многие. Основная сложность в её использовании - это необходимость в буквальном смысле шаблонной копипасте реализаций методов публичного класса. Шаблонность заключается в том, что вся реализация этих методов - это делегация вызова соответствующему методу приватного класса. То есть, положим, мы имеем такую декларацию публичного класса:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestPimplImpl;
     
    class TestPimpl : flex_lib::pimpl<TestPimplImpl>
    {
    public:
        explicit TestPimpl(uint32_t number = 0);
        explicit TestPimpl(std::string number);
        explicit TestPimpl(TestMoveable&& obj) noexcept;
        ~TestPimpl() noexcept;
        
        const std::string GetString() const;
        const uint32_t GetNumber() const;
        void SetGeneratedValues(unsigned values[10]);
        const std::array<unsigned, 10> GetGeneratedValues() const;
        void ResetValues(int num, std::string str);
        PimplMode GetCurrentMode() const;
        const int* GetMoveablePtr() const noexcept;
        
        bool operator == (const TestPimpl& other) const;
    };

Для каждого публичного метода в этой декларации (включая конструкторы) необходимо будет написать реализацию типа:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    void TestPimpl::ResetValues(int num, std::string str)
    {
        m_impl->ResetValues(num, std::move(str));
    }

Вот эти вот генерацией этих шаблонных реализаций методов и занимается генератор кода. Он проверяет характер сигнатуры функции, типы аргументов, где надо - применяет move-операции, и всё такое прочее. Есть желание к нему прикрутить так же чекер класса-реализации, чтобы он как минимум предупреждал, что сигнатуры методов поменялись, что-то добавилось. А как максимум - сам вносил необходимые "точечные" изменения.

Автор: Pavia 17.05.17, 16:02
Flex Ferrum
И как же разработчики виндоуса без всего этого обходились?

Автор: Flex Ferrum 17.05.17, 16:06
Цитата Pavia @
И как же разработчики виндоуса без всего этого обходились?

Ой, не знаю. :D

Автор: JoeUser 19.06.17, 19:33
Цитата Flex Ferrum @
Генерация pimpl-вызовов допилена и выложена в репозиторий.

Флекс, раскидай, пожалуйста, на пальцах - в чем профит?

Желательно просто по пунктам, типа "раньше вы делали вот так для такой задачи, и это было плохо, патамушта! .... а сейчас это здорово, и делается вот - так ... и это лучше патамушта...". Кстати, если не заленишься - можно будет пополнить раздел FAQ.

Автор: Flex Ferrum 19.06.17, 19:52
Цитата JoeUser @
Флекс, раскидай, пожалуйста, на пальцах - в чем профит?

Так я же в 7-ом посте как раз "на пальцах" всё и раскидал.

Автор: JoeUser 19.06.17, 19:57
Цитата Flex Ferrum @
Так я же в 7-ом посте как раз "на пальцах" всё и раскидал.

Упс ... 7-й пост, это какой? По номеру - это тот, которым ты толькашта ответил. Дай правильный линк на него, плс.

Автор: Flex Ferrum 19.06.17, 19:59
Но могу ещё раз. Есть фасадный класс:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestPimplImpl;
     
    enum PimplMode
    {
        NormalMode,
        AbnormalMode
    };
     
    class TestMoveable
    {
    public:
        TestMoveable(int value = 10)
            : m_object(new int(value))
        {
        }
        
        const int* GetObject() const {return m_object.get();}
        
    private:
        std::unique_ptr<int> m_object;
    };
     
    class TestPimpl : flex_lib::pimpl<TestPimplImpl>
    {
    public:
        explicit TestPimpl(uint32_t number = 0);
        explicit TestPimpl(std::string number);
        explicit TestPimpl(TestMoveable&& obj) noexcept;
        ~TestPimpl() noexcept;
        
        const std::string GetString() const;
        const uint32_t GetNumber() const;
        void SetGeneratedValues(unsigned values[10]);
        const std::array<unsigned, 10> GetGeneratedValues() const;
        void ResetValues(int num, std::string str);
        PimplMode GetCurrentMode() const;
        const int* GetMoveablePtr() const noexcept;
        
        bool operator == (const TestPimpl& other) const;
    };

Есть класс-реализация:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class TestPimpl;
     
    class TestPimplImpl
    {
    public:
        TestPimplImpl(TestPimpl*, uint32_t number)
            : m_intNumber(number)
        {        
        }
     
        TestPimplImpl(TestPimpl*, std::string number)
            : m_strNumber(number)
        {
        }
        
        TestPimplImpl(TestPimpl*, TestMoveable&& moveable)
            : m_moveableObj(std::move(moveable))
        {
        }
        
        const std::string GetString() const
        {
            return m_strNumber;
        }
        const uint32_t GetNumber() const
        {
            return m_intNumber;
        }
        void SetGeneratedValues(unsigned values[10])
        {
            std::copy(values, values + 10, begin(m_values));
        }
        const std::array<unsigned, 10> GetGeneratedValues() const
        {
            return m_values;
        }
        void ResetValues(int num, std::string str)
        {
            m_intNumber = num;
            m_strNumber = str;
        }
     
        PimplMode GetCurrentMode() const
        {
            return m_curMode;
        }
        const int* GetMoveablePtr() const
        {
            return m_moveableObj.GetObject();
        }
        
        bool operator == (const TestPimplImpl& pimpl) const
        {
            return m_intNumber == pimpl.m_intNumber;
        }
        
    private:
        uint32_t m_intNumber;
        std::string m_strNumber;
        std::array<unsigned, 10> m_values;
        PimplMode m_curMode = AbnormalMode;
        TestMoveable m_moveableObj = TestMoveable(5);
    };

Нужно реализовать методы фасадного класса так, чтобы они перевызывали методы класса-реализации. То есть ручками написать вот такой вот код:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    TestPimpl::TestPimpl(uint32_t number)
        : pimpl(this, number)
    {
    }
     
    TestPimpl::TestPimpl(std::string number)
        : pimpl(this, std::move(number))
    {
    }
     
    TestPimpl::TestPimpl(TestMoveable &&obj) noexcept
        : pimpl(this, std::move(obj))
    {
    }
     
    TestPimpl::~TestPimpl() noexcept = default;
     
    const std::string TestPimpl::GetString() const
    {
        return m_impl->GetString();
    }
     
    const uint32_t TestPimpl::GetNumber() const
    {
        return m_impl->GetNumber();
    }
     
    void TestPimpl::SetGeneratedValues(unsigned int values[10])
    {
        m_impl->SetGeneratedValues(values);
    }
     
    const std::array<unsigned int, 10> TestPimpl::GetGeneratedValues() const
    {
        return m_impl->GetGeneratedValues();
    }
     
    void TestPimpl::ResetValues(int num, std::string str)
    {
        m_impl->ResetValues(num, std::move(str));
    }
     
    PimplMode TestPimpl::GetCurrentMode() const
    {
        return m_impl->GetCurrentMode();
    }
     
    const int * TestPimpl::GetMoveablePtr() const noexcept
    {
        return m_impl->GetMoveablePtr();
    }
     
    bool TestPimpl::operator==(const TestPimpl &other) const
    {
        return m_impl->operator==(*other.m_impl.get());
    }

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

Добавлено
Цитата JoeUser @
Упс ... 7-й пост, это какой? По номеру - это тот, которым ты толькашта ответил. Дай правильный линк на него, плс.

Тьфу ты, второй, конечно же... Автоматизация для C++-программистов (сообщение #3722616)

Автор: JoeUser 19.06.17, 20:52
Цитата Flex Ferrum @
И, после того, как ты эту пачку типовых реализаций напишешь один раз

Флекс, не не не :) Я пока не хочу разбираться во всей этой кухне пока не пройму профит. Зачем это все??? Что решается, что упрощается, что оптимизируется??? Мне это важно.

Вижу портянки кода. Это же должно чем-то оправдываться? Вот именно это пока интересует.

Автор: Flex Ferrum 19.06.17, 21:15
Цитата JoeUser @
Флекс, не не не Я пока не хочу разбираться во всей этой кухне пока не пройму профит. Зачем это все???

А, тебе не понятно, что есть такое pimpl? Это один из самых популярных способов скрыть реализацию. То есть описать стабильный публичный интерфейс, и отдельно - приватный внутренний. Причём приватный пользователям публичного интерфейса не виден (Private IMPLementation). Профит в том, что после этого ты можешь совершенно свободно менять детали реализации не затрагивая клиентов публичного интерфейса.

Автор: Qraizer 20.06.17, 01:52
Цитата Flex Ferrum @
Профит в том, что после этого ты можешь совершенно свободно менять детали реализации не затрагивая клиентов публичного интерфейса.
Вообще-то это делает простая инкапсуляция с сокрытием кишок в непубличных элементах. PImpl имеет профит втрое круче:
  1. все преимущества чёрного ящика, скрытого от публичного интерфейса;
  2. непубличные элементы вообще не светятся в интерфейсе класса;
  3. модификация непубличных сущностей не требует даже перекомпиляции клиентов, а с случае .dll или там .so, даже перелинковки.
На предмет последнего я слегка сутрировал, это достигается посредством PPImpl :D

Автор: Flex Ferrum 09.11.17, 15:22
Тут вот возник вопрос про мультиметоды и автогенерацию кода. А ведь это возможно! И решение будет проще, чем на шаблонах. И, главное, универсальнее, т. к. можно поддержать мультиметоды с арностью больше двух.

Автор: Qraizer 09.11.17, 15:51
Flex Ferrum, ты мою тему помнишь? Там арность неограничена по сути. И кастомизируемое связывание для каждого параметра, хошь динамическое, хошь статическое.

Автор: Flex Ferrum 09.11.17, 16:13
Цитата Qraizer @
Flex Ferrum, ты мою тему помнишь? Там арность неограничена по сути. И кастомизируемое связывание для каждого параметра, хошь динамическое, хошь статическое.

Надо откопать.

Автор: Qraizer 09.11.17, 16:21
В натуре, как-то глубоко закопалась, с третьей попытки только Поиском нашёл.

Автор: Flex Ferrum 13.03.18, 09:22
Похоже, пришло время вернуться к тулзе. Следующий генератор будет создавать болванки gtest-тесткейсов для тестирования публичного интерфейса классов. Ну и заодно имплементацию моков.

Автор: Flex Ferrum 07.05.18, 07:42
Параллельно решил перевести генераторы на использование собственной реализации Jinja2 шаблонов для C++.
Эти шаблоны изначально делались под питона, но уж очень мощные получились, а достойной реализации под плюсы - нет.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)