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()
. , . .
:
, - .
, - , private.
, -, 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;
}