Python имеет очень полезную функцию функциональные декораторы, что, кроме того, позволяет состав. Например, если написать функцию foo
тогда вы можете заявить, что хотели бы foo
быть memoized, но также повторена более одного раза в случае отсутствия кэша, в котором foo
также выдвигает исключение:
@lru_cache
@retry
def foo(...):
Компоновка декоратора позволяет разрабатывать такие функции, как foo
и индивидуальные декораторы функций независимо, а затем смешивая их по мере необходимости. Было бы хорошо, если бы мы могли сделать это и в C ++ (насколько это возможно).
Хотя в StackOverflow есть несколько вопросов, касающихся декораторов функций, все они, похоже, генерируют несоставимые из-за жестких предположений о сигнатуре декорированной функции. Например, рассмотрим превосходный ответ этот вопрос. Украшение имеет форму
template <typename R, typename... Args>
std::function<R (Args...)> memo(R (*fn)(Args...)) {
Следовательно, это не может быть применено к самому результату (по общему признанию не большая проблема для определенного декоратора, использующего памятку).
Как тогда мы можем написать декораторы составных функций?
Еще один способ создания составные функциональные декораторы с помощью набора Mixin классы.
Следует минимальный рабочий пример:
#include<iostream>
#include<functional>
#include<utility>
#include<type_traits>
template<class T>
struct LoggerDecoratorA: public T {
template<class U>
LoggerDecoratorA(const U &u): T{u} { }
template<typename... Args>
auto operator()(Args&&... args) const ->
typename std::enable_if<
not std::is_same<
typename std::result_of<T(Args...)>::type,
void
>::value,
typename std::result_of<T(Args...)>::type>::type
{
using namespace std;
cout << "> logger A" << endl;
auto ret = T::operator()(std::forward<Args>(args)...);
cout << "< logger A" << endl;
return ret;
}
template<typename... Args>
auto operator()(Args&&... args) const ->
typename std::enable_if<
std::is_same<
typename std::result_of<T(Args...)>::type,
void
>::value,
typename std::result_of<T(Args...)>::type>::type
{
using namespace std;
cout << "> logger A" << endl;
T::operator()(std::forward<Args>(args)...);
cout << "< logger A" << endl;
}
};
template<class T>
struct LoggerDecoratorB: public T {
template<class U>
LoggerDecoratorB(const U &u): T{u} { }
template<typename... Args>
auto operator()(Args&&... args) const ->
typename std::enable_if<
not std::is_same<
typename std::result_of<T(Args...)>::type,
void
>::value,
typename std::result_of<T(Args...)>::type>::type
{
using namespace std;
cout << "> logger B" << endl;
auto ret = T::operator()(std::forward<Args>(args)...);
cout << "< logger B" << endl;
return ret;
}
template<typename... Args>
auto operator()(Args&&... args) const ->
typename std::enable_if<
std::is_same<
typename std::result_of<T(Args...)>::type,
void
>::value,
typename std::result_of<T(Args...)>::type>::type
{
using namespace std;
cout << "> logger B" << endl;
T::operator()(std::forward<Args>(args)...);
cout << "< logger B" << endl;
}
};
int main() {
std::function<int()> fn = [](){
using namespace std;
cout << 42 << endl;
return 42;
};
std::function<void()> vFn = [](){
using namespace std;
cout << "void" << endl;
};
using namespace std;
decltype(fn) aFn =
LoggerDecoratorA<decltype(fn)>(fn);
aFn();
cout << "---" << endl;
decltype(vFn) bVFn =
LoggerDecoratorB<decltype(vFn)>(vFn);
bVFn();
cout << "---" << endl;
decltype(fn) abFn =
LoggerDecoratorA<LoggerDecoratorB<decltype(fn)>>(fn);
abFn();
cout << "---" << endl;
decltype(fn) baFn =
LoggerDecoratorB<LoggerDecoratorA<decltype(fn)>>(fn);
baFn();
}
Я не уверен, что из упомянутых вами проблем это действительно решает, но не стесняйтесь просить об изменениях, и я постараюсь обновить его, если это возможно.
Одним из способов создания составных функций декораторов является ослабление предположения о сигнатуре, принятой декоратором. Скажем у нас
template<class Fn>
struct foo_decorator
{
template<typename ...Args>
auto operator()(Args &&...args) const ->
typename std::result_of<Fn(Args...)>::type;
};
template<class Fn>
foo_decorator<Fn> make_foo(const Fn &fn) {return foo_decorator<Fn>();}
Как видно, оба make_foo
а также foo_decorator
параметризованы class Fn
который может быть практически любым в этой точке. Следовательно, они могут взять и лямбда-функцию или функтор, например. Взятые аргументы (и возвращаемый тип) (время компиляции) откладываются до вызова, где выведенные параметры шаблона при вызове функции C ++ будут заполнять остальные детали по мере необходимости.
Используя это, вот простой декоратор регистрации:
#include <type_traits>
#include <cmath>
#include <iostream>
template<class Fn>
struct logger
{
logger(const Fn &fn, const std::string &name) : m_fn(fn), m_name{name}{}
template<typename ...Args>
auto operator()(Args &&...args) const ->
typename std::result_of<Fn(Args...)>::type
{
std::cout << "entering " << m_name << std::endl;
const auto ret = m_fn(std::forward<Args>(args)...);
std::cout << "leaving " << m_name << std::endl;
return ret;
}
private:
Fn m_fn;
std::string m_name;
};
template<class Fn>
logger<Fn> make_log(const Fn &fn, const std::string &name)
{
return logger<Fn>(fn, name);
}
int main()
{
auto fn = make_log([](double x){return std::sin(x);}, "sin");
std::cout << fn(4.0) << std::endl;
}
Вот это сборка этого декоратора, Вот это сборка повторяющегося декоратора, и Вот это сборка композиции из них.
Одним из недостатков этого подхода является случай, когда декоратор имеет состояние, основанное на сигнатуре функции, например, исходный случай запоминания. С этим можно справиться, используя стирание типа (см. Сборку Вот), но это имеет ряд недостатков, одним из которых является то, что ошибки, которые концептуально могли быть обнаружены во время компиляции, теперь обнаруживаются во время выполнения (когда стирание типа обнаруживает незаконное использование).