Pergunta

No meu local de trabalho, temos a tendência de uso iostream , string , vector , Mapa , eo estranho algoritmo ou dois. Nós realmente não tenho encontrado muitas situações em que as técnicas de modelo eram uma melhor solução para um problema.

O que eu estou procurando aqui são idéias e código opcionalmente exemplo que mostra como você usou uma técnica modelo para criar uma nova solução para um problema que você encontrou na vida real.

Como um suborno, esperam uma votação para a sua resposta.

Foi útil?

Solução

Eu usei um monte de código do modelo, principalmente em Boost e do STL, mas eu raramente tinha a necessidade de write qualquer.

Uma das exceções, há alguns anos, estava em um programa que manipulados arquivos EXE PE formato Windows. A empresa queria acrescentar suporte a 64 bits, mas a classe ExeFile que eu tinha escrito para manipular os arquivos só trabalhou com os de 32 bits. O código necessário para manipular a versão de 64 bits era essencialmente idêntica, mas necessária para usar um tipo de endereço diferente (de 64 bits em vez de 32 bits), o que causou duas outras estruturas de dados a ser bem diferentes.

Com base no uso da STL de um único modelo para apoiar tanto std::string e std::wstring, decidi tentar fazer ExeFile um modelo, com as estruturas de dados diferentes e o tipo de endereço como parâmetros. Havia dois lugares onde eu ainda tinha que usar linhas #ifdef WIN64 (ligeiramente diferentes requisitos de processamento), mas não foi realmente difícil de fazer. Temos o apoio total de 32 e 64 bits em que o programa agora, e usando os meios de modelo que todas as modificações que fizemos desde automaticamente se aplica a ambas as versões.

Outras dicas

Informações gerais sobre modelos:

Os modelos são a qualquer hora útil que você precisa usar o mesmo código, mas operando em diferentes tipos de dados, onde os tipos são conhecidos em tempo de compilação. E também quando você tem qualquer tipo de objeto de recipiente.

Um uso muito comum é para apenas sobre cada tipo de estrutura de dados. Por exemplo: listas Singly ligados, listas duplamente ligadas, árvores, tentativas, hashtables, ...

Outro uso muito comum é para algoritmos de ordenação.

Uma das principais vantagens da utilização de modelos é que você pode remover a duplicação de código. duplicação de código é uma das maiores coisas que você deve evitar quando a programação.

Você poderia implementar uma função Max tanto como uma macro ou um modelo, mas a implementação do modelo seria tipo de seguro e, portanto, melhor.

E agora para as coisas legais:

Veja também template metaprogramming , que é uma forma de código de pré-avaliar em tempo de compilação em vez de em tempo de execução. metaprogramming modelo tem apenas variáveis ??imutáveis ??e, portanto, suas variáveis ??não pode mudar. Devido a este modelo metaprogramming pode ser visto como um tipo de programação funcional.

Confira este exemplo de modelo metaprogramming da Wikipedia. Ele mostra como os modelos podem ser usados ??para executar código em tempo de compilação . Por isso em tempo de execução que você tem uma constante pré-calculado.

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}

Um lugar que eu utilizar modelos para criar o meu próprio código é implementar as classes políticas, como descrito por Andrei Alexandrescu em Design ++ Modern C. No momento eu estou trabalhando em um projeto que inclui um conjunto de classes que interagem com o BEA \ h \ h \ h do monitor Tuxedo TP da Oracle.

Uma instalação que Tuxedo fornece é filas persistentes transacionais, então eu tenho uma classe TpQueue que interage com a fila:

class TpQueue {
public:
   void enqueue(...)
   void dequeue(...)
   ...
}

No entanto, como a fila é transacional I necessidade de decidir o comportamento de transação que eu quero; isso poderia ser feito separadamente fora da classe TpQueue mas eu acho que é mais explícito e menos propenso a erros, se cada instância TpQueue tem sua própria política de transações. Então, eu tenho um conjunto de classes TransactionPolicy tais como:

class OwnTransaction {
public:
   begin(...)  // Suspend any open transaction and start a new one
   commit(..)  // Commit my transaction and resume any suspended one
   abort(...)
}

class SharedTransaction {
public:
   begin(...)  // Join the currently active transaction or start a new one if there isn't one
   ...
}

E a classe TpQueue recebe re-escrito como

template <typename TXNPOLICY = SharedTransaction>
class TpQueue : public TXNPOLICY {
   ...
}

Assim, dentro TpQueue eu posso chamar begin (), abort (), commit (), conforme necessário, mas pode alterar o comportamento baseado na maneira que eu declare a ocorrência de:

TpQueue<SharedTransaction> queue1 ;
TpQueue<OwnTransaction> queue2 ;

Eu usei modelos (com a ajuda de Boost.Fusion) para alcançar inteiros tipo seguro para uma biblioteca hipergrafo que eu estava desenvolvendo. Eu tenho um (hiper) ID de ponta e um ID vértice sendo que ambos são inteiros. Com modelos, vértice e hyperedge IDs tornou-se diferentes tipos e usando um quando o outro era esperado gerado um erro em tempo de compilação. Salva-me um monte de dor de cabeça que eu de outra forma ter com a depuração de tempo de execução.

Aqui está um exemplo de um projeto real. Eu tenho funções getter como esta:

bool getValue(wxString key, wxString& value);
bool getValue(wxString key, int& value);
bool getValue(wxString key, double& value);
bool getValue(wxString key, bool& value);
bool getValue(wxString key, StorageGranularity& value);
bool getValue(wxString key, std::vector<wxString>& value);

E, em seguida, uma variante com o valor 'default'. Ele retorna o valor para a chave se existe, ou o valor padrão se isso não acontece. Template me salvou de ter que criar 6 novas funções mim mesmo.

template <typename T>
T get(wxString key, const T& defaultValue)
{
    T temp;
    if (getValue(key, temp))
        return temp;
    else
        return defaultValue;
}

Modelos I regularmente consomem uma infinidade de classes container, aumentar ponteiros inteligentes, scopeguards , algoritmos alguns STL.

Cenários em que tenho escrito modelos:

  • recipientes personalizados
  • gerenciamento de memória, a implementação de segurança de tipo e ctor / dtor invocação no topo de void * allocators
  • implementação comum para sobrecargas wiht tipos diferentes, por exemplo.

    bool ContainsNan (float *, int) bool ContainsNan (* double, int)

que ambos apenas chamar um (local, escondido) função auxiliar

template <typename T>
bool ContainsNanT<T>(T * values, int len) { ... actual code goes here } ;

algoritmos específicos que são independentes do tipo, desde que o tipo tem certas propriedades, por exemplo serialização binária.

template <typename T>
void BinStream::Serialize(T & value) { ... }

// to make a type serializable, you need to implement
void SerializeElement(BinStream & strean, Foo & element);
void DeserializeElement(BinStream & stream, Foo & element)

Ao contrário das funções virtuais, modelos permitir que mais otimizações a ter lugar.


Geralmente, os modelos permitem implementar um conceito ou algoritmo para uma infinidade de tipos, e ter as diferenças já resolvidas em tempo de compilação.

Usamos COM e aceitar um apontador para um objecto que pode aplicar directa ou através de [IServiceProvider] outra interface ( http://msdn.microsoft.com/en-us/library/cc678965 (VS.85) .aspx) isso me levou a criar esta helper cast-like função.

// Get interface either via QueryInterface of via QueryService
template <class IFace>
CComPtr<IFace> GetIFace(IUnknown* unk)
{
    CComQIPtr<IFace> ret = unk; // Try QueryInterface
    if (ret == NULL) { // Fallback to QueryService
        if(CComQIPtr<IServiceProvider> ser = unk)
            ser->QueryService(__uuidof(IFace), __uuidof(IFace), (void**)&ret);
    }
    return ret;
}

Eu uso modelos para especificar os tipos de objetos de função. Eu código muitas vezes escrita que leva um objeto de função como um argumento - uma função de integrar, uma função para otimizar, etc. - e eu encontrar modelos mais conveniente do que a herança. Então meu código de receber um objeto de função - como um integrador ou otimizador -. Tem um parâmetro de modelo para especificar o tipo de objeto de função que opera em

As razões óbvias (como prevenção de code-duplicação, operando em diferentes tipos de dados) de lado, há esse padrão muito legal que é chamado de formulação de políticas baseadas. Eu tenho uma pergunta sobre vs estratégias .

Agora, o que é tão bacana sobre esse recurso. Considere que você está escrevendo uma interface para que outros possam usar. Você sabe que sua interface será usado, porque é um módulo em seu próprio domínio. Mas você ainda não sabe como as pessoas vão usá-lo. design baseado em política fortalece seu código para futura reutilização; isso faz você independente de tipos de dados uma implementação específica depende. O código é apenas "slurped in". : -)

Os traços são por si só uma idéia maravilhosa. Eles podem atribuir especial comportamento, dados e typedata a um modelo. Traços permitir parametrização completa de todos esses três campos. E o melhor de tudo, é uma boa forma de reutilizável código make.

Uma vez vi o seguinte código:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   // three lines of code
   callFunctionGeneric1(c) ;
   // three lines of code
}

repetida dez vezes:

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
void doSomethingGeneric3(SomeClass * c, SomeClass & d)
void doSomethingGeneric4(SomeClass * c, SomeClass & d)
// Etc

Cada função tendo os mesmos 6 linhas de código cópia / colado, e cada vez que uma outra função de chamada callFunctionGenericX com o mesmo número de sufixo.

Não houve maneira de refazer a coisa toda completamente. Então eu continuei a refatoração local.

Eu mudei o código dessa maneira (de memória):

template<typename T>
void doSomethingGenericAnything(SomeClass * c, SomeClass & d, T t)
{
   // three lines of code
   t(c) ;
   // three lines of code
}

E modificado o código existente com:

void doSomethingGeneric1(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric1) ;
}

void doSomethingGeneric2(SomeClass * c, SomeClass & d)
{
   doSomethingGenericAnything(c, d, callFunctionGeneric2) ;
}

Etc.

Este é um pouco sequestro a coisa modelo, mas no final, eu acho que é melhor do que jogar com ponteiros de função typedefed ou usando macros.

Eu, pessoalmente, ter usado a configuração padrão Curiosamente recorrente como um meio de impor alguma forma de projeto top-down e implementação de baixo para cima. Um exemplo seria uma especificação para um manipulador genérico onde certos requisitos de forma e de interface são impostas sobre os tipos de derivados em tempo de compilação. Parece algo como isto:

template <class Derived>
struct handler_base : Derived {
  void pre_call() {
    // do any universal pre_call handling here
    static_cast<Derived *>(this)->pre_call();
  };

  void post_call(typename Derived::result_type & result) {
    static_cast<Derived *>(this)->post_call(result);
    // do any universal post_call handling here
  };

  typename Derived::result_type
  operator() (typename Derived::arg_pack const & args) {
    pre_call();
    typename Derived::result_type temp = static_cast<Derived *>(this)->eval(args);
    post_call(temp);
    return temp;
  };

};

Algo como isso pode ser usado, em seguida, para se certificar de seus manipuladores de derivar a partir desse modelo e aplicar projeto top-down e, em seguida, permitir a personalização de baixo para cima:

struct my_handler : handler_base<my_handler> {
  typedef int result_type; // required to compile
  typedef tuple<int, int> arg_pack; // required to compile
  void pre_call(); // required to compile
  void post_call(int &); // required to compile
  int eval(arg_pack const &); // required to compile
};

Isso, então, permite que você tenha funções polimórficas genéricos que lidam com apenas handler_base <> tipos derivados:

template <class T, class Arg0, class Arg1>
typename T::result_type
invoke(handler_base<T> & handler, Arg0 const & arg0, Arg1 const & arg1) {
  return handler(make_tuple(arg0, arg1));
};

Já foi mencionado que você pode usar modelos como classes políticas para fazer algo . Eu uso muito isso.

Eu também usá-los, com a ajuda de mapas de propriedade ( consulte o site impulso para mais informações sobre este ), a fim de acessar os dados de uma forma genérica. Isso dá a oportunidade de mudar a maneira de armazenar dados, sem ter que mudar a maneira de recuperá-lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top