Será sobre um padrão tão simples, mas frequentemente usado, como cadeia de responsabilidade. A essência do padrão é que usamos vários manipuladores para processar um evento, cada um dos quais decide o que e quando passar para o próximo. Existem muitos exemplos de implementações de C ++ na rede, mas quero mostrar a implementação apenas em expressões lambda. Nesta implementação, será possível observar um pouco da mágica dos modelos de
rua .
Então, digamos que temos um 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; };
E teremos 3 manipuladores, cada um dos quais transmitirá o objeto ainda mais:
Neste exemplo, existem três manipuladores, cada um dos quais é uma expressão lambda, que são combinados em uma cadeia de responsabilidades usando a classe ChainOfRepsonsibility.
Aqui está a própria implementação da classe:
#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 assim:
- Primeiro, criamos uma cadeia de responsabilidade usando a função start_new. O principal problema nesse estágio é obter uma lista de argumentos do lambda passado e criar um "protótipo" do manipulador com base neles.
- Para obter os argumentos lambda, são utilizadas a classe StartChain e um tema complicado com especialização de modelos. Primeiro, declaramos uma versão genérica da classe e, em seguida, a estrutura de especialização StartChain <void (C :: *) (Args ...) const> . Essa construção nos permite acessar a lista de argumentos pelo membro da classe passado.
- Tendo uma lista de argumentos, já podemos criar as classes ChainTail e Chain, que serão um wrapper para os manipuladores. Sua tarefa é salvar o lambda na função std :: e chamá-lo no momento certo, além de implementar o método attach (configurando o manipulador de cima).
O código foi testado no visual studio 2015. Para o gcc, pode ser necessário corrigir a especialização StartChain removendo a const de lá. Se, além das lambdas, você apenas deseja passar funções, você pode adicionar outra especialização StartChain. Você ainda pode tentar se livrar da cópia em anexo, mover-se, mas, para não complicar o exemplo, deixei apenas o caso mais simples.