Я пытаюсь уменьшить дублирование кода в классе, который реализует идиому 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++. У них много минусов. Вместо этого вы должны попытаться использовать шаблоны (функций), если можете.
Как в этой онлайн-демонстрации: 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;
}
Это решение прекрасно!! Большое спасибо. Мой следующий вопрос был бы о том, как обрабатывать возвращаемые значения, потому что мне также нужно их регистрировать. С этим решением это также возможно. Отличный ответ!
Класс RAII может позволить избавиться от условия Demo.
Отвечает ли это на ваш вопрос? Автоматизируйте создание классов C++ — есть ли простой способ?