For whom
This article is intended for those who have not encountered the Curiously recurring template pattern (CRTP) idiom, but have an idea of โโwhat templates are in C ++. You will not need specific knowledge or firm knowledge of programming in templates to understand the article.
Let us have this problem:
A file comes from the network in one of the formats: json or xml and we want to parse them and get some information. The solution suggests itself - to use the bridge pattern to separate the parser interface and its two implementations, one per file format. So, after determining the file format, we can pass the implementation we need in the form of a pointer to the parsing function.
Schematic example
// , 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);
}
This classic approach has several drawbacks :
The Parser interface must have virtual functions, and as we know, it is expensive to go to the virtual method table.
The function interface is not as descriptive as we would like when compared, for example, to functional languages โโwith rich type systems.
( , ).
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
. . , :
. , , .
: , , , .
This approach is also used for the MixIn idiom of classes, which "mix" their behavior with inherited classes. One of these classes - std::enable_shared_from_this
- mixes in functionality to get a pointer shared_ptr
to itself.
This article provides the simplest example to familiarize yourself with the topic, further - more.
Complete listing of working code
#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;
}