CRTP: ejemplo del patrón "Puente"

Para quien

Este artículo está dirigido a aquellos que no se han encontrado con el modismo del patrón de plantilla curiosamente recurrente (CRTP), pero tienen una idea de qué plantillas hay en C ++. No necesitará conocimientos específicos o conocimientos firmes de programación en plantillas para comprender el artículo.





Tengamos este problema:





Un archivo viene de la red en uno de los formatos: json o xml y queremos analizarlos y obtener información. La solución se sugiere a sí misma: usar el patrón de puente para separar la interfaz del analizador y sus dos implementaciones, una por formato de archivo. Entonces, después de determinar el formato de archivo, podemos pasar la implementación que necesitamos en forma de puntero a la función de análisis.





Ejemplo 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);
}
      
      



Este enfoque clásico tiene varios inconvenientes :





  • La interfaz del analizador debe tener funciones virtuales y, como sabemos, es caro ir a la tabla de métodos virtuales.





  • La interfaz de funciones no es tan descriptiva como nos gustaría que fuera en comparación, por ejemplo, con lenguajes funcionales con sistemas de tipos enriquecidos.





  • ( , ).





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



. . , :





  • . , , .





  • : , , , .





Este enfoque también se utiliza para el lenguaje de clases MixIn, que "mezcla" su comportamiento con clases heredadas. Una de estas clases, std::enable_shared_from_this



combina la funcionalidad para obtener un puntero shared_ptr



a sí misma.





Este artículo proporciona el ejemplo más simple para familiarizarse con el tema, además, más.





Lista completa de código de trabajo

#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