Идиома Pimpl через макрос

Я пытаюсь уменьшить дублирование кода в классе, который реализует идиому pimpl.

Представьте, что у меня есть заголовок Foo.h. Для лучшей читабельности я сократил методы.

class FooImp;

class Foo
{
public:
    void A(int x);
    void B(int x, double b);

private:
    FooImp* _imp;
};

Затем у меня есть файл реализации, подобный следующему:

class FooImp
{
public:
    void A(int x) {}
    void B(int x, double b) {}
};

class Logger{};
class Measure{};

void Foo::A(int x)
{
    Measure m;
    _imp->A(x);
    Logger log;
}

void Foo::B(int x, double b)
{
    Measure m;
    _imp->B(x, b);
    Logger log;
}

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

#define PIMPL_FUNCTION(x, ...)  \
    void Foo::x                 \
    {                           \
        Measure m;              \
        ptr->x(...);            \  
        Logger log;             \
    }                           \

PIMPL_FUNCTION(A(int x), x);
PIMPL_FUNCTION(B(int x, double b), x, b)    

Конечно, этот фрагмент кода не компилируется. Я не уверен, возможно ли то, что я пытаюсь, даже с макросами. Я открыт для любых идей или предложений.

Немного фона. Все эти методы будут API-вызовами для библиотеки, и количество методов будет увеличиваться.

Отвечает ли это на ваш вопрос? Автоматизируйте создание классов C++ — есть ли простой способ?

Karl Knechtel 19.04.2023 07:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Макросы — это не первое, что вы должны попробовать в C++. У них много минусов. Вместо этого вы должны попытаться использовать шаблоны (функций), если можете.

Как в этой онлайн-демонстрации: https://onlinegdb.com/QXbtOnhYeR

И исходный код:

#include <iostream>
#include <memory>

class FooImp
{
public:
    void A(int x)
    {
        std::cout << "FooImp::A(" << x << ")\n";
    }

    int B(int x)
    {
        std::cout << "FooImp::B(" << x << ")\n";
        return x * x;
    }
};

class Foo
{
public:
    Foo() : 
        _imp{ std::make_unique<FooImp>() }
    {
    }

    void A(int x)
    {
        // use a lambda funtion to call _imp->A
        call_impl([&] { _imp->A(x); });
    }

    int B(int x)
    {
        // use a lambda funtion to call _imp->B to show return values work too
        return call_impl([&] { return _imp->B(x); });
    }

private:
    // No MACRO use a function template 
    // see lamdba functions https://en.cppreference.com/w/cpp/language/lambda
    // decltype(fn()) = return type of function fn
    template<typename fn_t>
    auto call_impl(fn_t fn) -> decltype(fn()) // pass a function object (lambda/std::function)
    {
        std::cout << "Construct Measure here\n";
        

        if constexpr (std::is_same_v<void, decltype(fn()) >)
        {
            fn();
            std::cout << "Construct Log here\n";
        }
        else
        {
            auto retval = fn();
            std::cout << "Construct Log here\n";
            return retval;
        }
    }

    std::unique_ptr<FooImp> _imp; // do NOT use raw pointers
};

int main()
{
    Foo foo;
    foo.A(1);
    std::cout << foo.B(2) << "\n";
    return 0;
}

Это решение прекрасно!! Большое спасибо. Мой следующий вопрос был бы о том, как обрабатывать возвращаемые значения, потому что мне также нужно их регистрировать. С этим решением это также возможно. Отличный ответ!

RoQuOTriX 19.04.2023 13:57

Класс RAII может позволить избавиться от условия Demo.

Jarod42 19.04.2023 14:58

Другие вопросы по теме