誰のため
この記事は、不思議な繰り返しテンプレートパターン(CRTP)のイディオムに出くわしたことはないが、C ++のテンプレートについては知っている人を対象としています。記事を理解するために、テンプレートでのプログラミングに関する特定の知識や確固たる知識は必要ありません。
この問題を抱えてみましょう:
ファイルは、jsonまたはxmlのいずれかの形式でネットワークから取得されます。これらを解析して情報を取得します。このソリューションは、ブリッジパターンを使用して、パーサーインターフェイスとその2つの実装をファイル形式ごとに1つずつ分離することを提案しています。したがって、ファイル形式を決定した後、必要な実装を解析関数へのポインターの形式で渡すことができます。
概略例
// , 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);
}
この古典的なアプローチにはいくつかの欠点があります。
パーサーインターフェイスには仮想関数が必要であり、ご存知のとおり、仮想メソッドテーブルに移動するにはコストがかかります。
関数型インターフェースは、たとえば、リッチタイプシステムを備えた関数型言語と比較したときに、私たちが望むほど説明的ではありません。
( , ).
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
. . , :
. , , .
: , , , .
このアプローチは、クラスのMixInイディオムにも使用されます。このイディオムは、継承されたクラスと動作を「混合」します。これらのクラスの1つstd::enable_shared_from_this
---機能を組み合わせて、shared_ptr
それ自体へのポインタを取得します。
この記事では、このトピックに慣れるための最も簡単な例を紹介します。
作業コードの完全なリスト
#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;
}