CRTP: Exemplo no padrão "Bridge"

Para quem

Este artigo é direcionado para aqueles que não encontraram o idioma padrão de modelo curiosamente recorrente (CRTP), mas têm uma ideia do que os modelos estão em C ++. Você não precisará de conhecimento específico ou conhecimento firme de programação em modelos para entender o artigo.





Deixe-nos resolver este problema:





Um arquivo vem da rede em um dos formatos: json ou xml e queremos analisá-los e obter algumas informações. A solução se sugere - usar o padrão de ponte para separar a interface do analisador e suas duas implementações, uma por formato de arquivo. Assim, após determinar o formato do arquivo, podemos passar a implementação de que precisamos na forma de um ponteiro para a função de análise.





Exemplo esquemático

// ,     Parser   
//  ,     
ParsedDataType parseData(Parser* parser, FileType file);

int main() {
    FileType file = readFile();
    Parser* impl = nullptr;
    if (file.type() == JsonFile)
        impl = new ParserJsonImpl();
    else
        impl = new ParserXmlImpl();
    ParsedDataType parsedData = parserData(impl, file);
}
      
      



Essa abordagem clássica tem várias desvantagens :





  • A interface do Parser deve ter funções virtuais e, como sabemos, é caro ir para a tabela de métodos virtuais.





  • A interface de função não é tão descritiva quanto gostaríamos que fosse quando comparada, por exemplo, a linguagens funcionais com sistemas de tipo rico.





  • ( , ).





C++





CRTP - , , , .





- -, , , , .





template <typename Implementation>
struct ParserInterface {

    ParsedData getData() {
        return impl()->getDataImpl();
    }

    ParsedID getID() {
        return impl()->getIDImpl();    
    }

private:
    Implementation* impl() {
        return static_cast<Implementation*>(this);
    }
};
      
      



, , Implementation* impl()



.





-, . , -, .









struct ParserJsonImpl : public ParserInterface<ParserJsonImpl> {
    friend class ParserInterface;
private:
    ParsedData getDataImpl() {
        std::cout << "ParserJsonImpl::getData()\n";
        return ParsedData();
    }

    ParsedID getIDImpl() {
        std::cout << "ParserJsonImpl::getID()\n";
        return ParsedID;    
    }
};

struct ParserXmlImpl : public ParserInterface<ParserXmlImpl> {
    friend class ParserInterface;
private:
    ParsedData getDataImpl() {
        std::cout << "ParserXmlImpl::getData()\n";
        return ParsedData();
    }

    ParsedID getIDImpl() {
        std::cout << "ParserXmlImpl::getID()\n";
        return ParsedID();    
    }
};
      
      



, . , , ParserInterface<A>



ParserInterface<B>



. . , , - , static_cast<>()



Implementation* impl()



. , . .





:





  1. , - .





  2. , - , private.





  3. , -, friend.





, , .





template <typename Impl>
std::pair<ParsedData, parsedID> parseFile(ParserInterface<Impl> parser) {
    return std::make_pair(parser.getData(), parser.getID());
}
      
      



, . ParserInterface parser



. , static_cast



, , .





:

int main() {

    ParserJsonImpl jsonParser;
    parseFile(jsonParser);

    ParserXmlImpl xmlParser;
    parseFile(xmlParser);      

    return 0;
}
      
      



.





ParserJsonImpl::getData()
ParserJsonImpl::getID()
ParserXmlImpl::getData()
ParserXmlImpl::getID()

      
      



, , , . , static_cast



. . , :





  • . , , .





  • : , , , .





Essa abordagem também é usada para o idioma de classes MixIn, que "mistura" seu comportamento com classes herdadas. Uma dessas classes - std::enable_shared_from_this



- combina funcionalidades para obter um ponteiro shared_ptr



para si mesma.





Este artigo fornece o exemplo mais simples para se familiarizar com o tópico, ainda mais - mais.





Lista completa de código de trabalho

#include <iostream>


template <typename Implementation>
struct ParserInterface {
    int getData() {
        return impl()->getDataImpl();
    }

    int getID() {
        return impl()->getIDImpl();    
    }

private:
    Implementation* impl() {
        return static_cast<Implementation*>(this);
    }
};

struct ParserJsonImpl : public ParserInterface<ParserJsonImpl> {
    friend class ParserInterface<ParserJsonImpl>;
private:
    int getDataImpl() {
        std::cout << "ParserJsonImpl::getData()\n";
        return 0;
    }

    int getIDImpl() {
        std::cout << "ParserJsonImpl::getID()\n";
        return 0;
    }
};

struct ParserXmlImpl : public ParserInterface<ParserXmlImpl> {

    int getDataImpl() {
        std::cout << "ParserXmlImpl::getData()\n";
        return 0;
    }

    int getIDImpl() {
        std::cout << "ParserXmlImpl::getID()\n";
        return 0;    
    }
};

template <typename Impl>
std::pair<int, int> parseFile(ParserInterface<Impl> parser) {
    auto result = std::make_pair(parser.getData(), parser.getID());
    return result;
}


int main() {

    ParserJsonImpl jsonParser;
    parseFile(jsonParser);

    ParserXmlImpl xmlParser;
    parseFile(xmlParser);

    return 0;
}
      
      






All Articles