Se tratará de un patrón tan simple, pero a menudo utilizado como cadena de responsabilidad. La esencia del patrón es que usamos varios controladores para procesar un evento, cada uno de los cuales decide qué y cuándo pasar al siguiente. Hay muchos ejemplos de implementaciones de C ++ en la red, pero quiero mostrar la implementación solo en expresiones lambda. En esta implementación será posible observar una pequeña plantilla de
calle mágica.
Entonces digamos que tenemos un objeto:
class Elephant { public: std::string touch_leg() { return "it's like a pillar"; } std::string touch_trunk() { return "it's like a snake"; } std::string touch_tail() { return "it's like a rope"; } void run_away() { m_is_gone = true; std::cout << "*** Sound of running out elephant ***\n"; } bool is_elephant_here() { return !m_is_gone; } private: bool m_is_gone = false; };
Y tendremos 3 controladores, cada uno de los cuales pasará el objeto aún más:
En este ejemplo, hay 3 controladores, cada uno de los cuales es una expresión lambda, que se combinan en una cadena de responsabilidad utilizando la clase ChainOfRepsonibility.
Aquí está la implementación de la clase en sí:
#include <functional> struct ChainOfRepsonsibility { template<typename... Args> struct Chain { template<typename Callee, typename Next> Chain(const Callee c, const Next& n) { m_impl = c; m_next = n; } template<typename Callee> decltype(auto) attach(Callee c) { return Chain(c, *this); } void operator()(Args... e) { m_impl(e..., m_next); } std::function<void(Args..., std::function<void(Args...)>)> m_impl; std::function<void(Args...)> m_next; }; template<typename... Args> struct ChainTail { template<typename Callee> ChainTail(Callee c) { m_impl = c; } template<typename Callee> decltype(auto) attach(Callee c) { return Chain<Args...>(c, m_impl); } void operator()(Args... e) { m_impl(e...); } std::function<void(Args... e)> m_impl; }; template<typename> struct StartChain; template<typename C, typename... Args> struct StartChain<void (C::*)(Args...) const> { using Type = ChainTail<Args...>; }; template<typename Callee> static decltype(auto) start_new(Callee c) { return StartChain<decltype(&Callee::operator())>::Type(c); } };
Funciona así:
- Primero, creamos una cadena de responsabilidad utilizando la función start_new. El principal problema en esta etapa es obtener una lista de argumentos de la lambda aprobada y crear un "prototipo" del controlador basado en ellos.
- Para obtener los argumentos lambda, se utilizan la clase StartChain y un tema complicado con especialización de plantillas. Primero, declaramos una versión genérica de la clase, y luego la estructura de especialización StartChain <void (C :: *) (Args ...) const> . Esta construcción nos permite acceder a la lista de argumentos del miembro de la clase aprobada.
- Al tener una lista de argumentos, ya podemos crear las clases ChainTail y Chain, que serán un contenedor para los manejadores. Su tarea es guardar el lambda en std :: function y llamarlo en el momento adecuado, así como implementar el método de conexión (configurando el controlador desde arriba).
El código se ha probado en Visual Studio 2015, para gcc puede ser necesario corregir la especialización de StartChain eliminando el const de allí, si además de lambdas solo desea pasar funciones, puede agregar otra especialización de StartChain. Todavía puede intentar deshacerse de la copia en adjuntar, mover, pero para no complicar el ejemplo, dejé solo el caso más simple.