Pour qui
Cet article est destiné à ceux qui n'ont pas rencontré l'idiome du modèle de modèle curieusement récurrent (CRTP), mais qui ont une idée de ce que les modèles sont en C ++. Vous n'aurez pas besoin de connaissances spécifiques ou d'une connaissance approfondie de la programmation dans les modèles pour comprendre l'article.
Laissez-nous ce problème:
Un fichier provient du réseau dans l'un des formats: json ou xml et nous voulons les analyser et obtenir des informations. La solution se suggère: utiliser le modèle de pont pour séparer l'interface de l'analyseur et ses deux implémentations, une par format de fichier. Ainsi, après avoir déterminé le format de fichier, nous pouvons transmettre l'implémentation dont nous avons besoin sous la forme d'un pointeur vers la fonction d'analyse.
Exemple schématique
// , 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);
}
Cette approche classique présente plusieurs inconvénients :
L'interface Parser doit avoir des fonctions virtuelles, et comme nous le savons, il est coûteux d'accéder à la table des méthodes virtuelles.
L'interface de fonction n'est pas aussi descriptive que nous le souhaiterions lorsqu'elle est comparée, par exemple, à des langages fonctionnels avec des systèmes de types riches.
( , ).
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
. . , :
. , , .
: , , , .
Cette approche est également utilisée pour l'idiome MixIn des classes, qui «mélange» leur comportement avec les classes héritées. L'une de ces classes - std::enable_shared_from_this
- mélange des fonctionnalités pour obtenir un pointeur shared_ptr
sur elle-même.
Cet article fournit l'exemple le plus simple pour vous familiariser davantage avec le sujet - plus.
Liste complète du code de travail
#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;
}