ポリシー

テンプレートのおかげで、ソフトウェア設計の新しい方法があります。ポリシーと特性は、C++ で一般的に使用される 2 つのイディオムです。

ポリシーと特性は、多くの場合、1 つの文で使用されます。ポリシーから始めましょう。

ポリシー

ポリシーは、動作を構成できる汎用関数またはクラスです。通常、ポリシー パラメータにはデフォルト値があります。 std::vector そして std::unordered_map

template<class T, class Allocator = std::allocator<T>> // (1)
class vector; 

template<class Key,
 class T,
 class Hash = std::hash<Key>,  // (3)
 class KeyEqual = std::equal_to<Key>,  // (4)
 class allocator = std::allocator<std::pair<const Key, T>> // (2)
class unordered_map;

これは、 T に応じて、各コンテナにその要素のデフォルト アロケータがあることを意味します。 (1 行目) または std::pair<const Key, T> で (2行目)。さらに、 std::unorderd_map デフォルトのハッシュ関数 (3 行目) とデフォルトの等号関数 (4) があります。ハッシュ関数はキーに基づいてハッシュ値を計算し、equal 関数はバケット内の衝突を処理します。以前の記事「ハッシュ関数」では、 std::unordered_map について詳しく説明しています。 .

ユーザー定義のデータ型 MyInt を使用させてください std::unordered_map. のキーとして

// MyIntAsKey.cpp

#include <iostream>
#include <unordered_map>

struct MyInt{
 explicit MyInt(int v):val(v){}
 int val;
};

int main(){

 std::cout << '\n';

 std::unordered_map<MyInt, int> myMap{ {MyInt(-2), -2}, {MyInt(-1), -1}, 
 {MyInt(0), 0}, {MyInt(1), 1} };

 std::cout << "\n\n";

}

MyInt のため、コンパイルはかなり言葉を尽くして失敗します はハッシュ関数もイコール関数もサポートしていません。

ここで、ポリシーが開始されます。ポリシー パラメーターを置き換えることができます。次のクラス MyInt したがって、std::unordered_map. のキーとして使用できます。

// templatesPolicy.cpp

#include <iostream>
#include <unordered_map>

struct MyInt{
 explicit MyInt(int v):val(v){}
 int val;
};

struct MyHash{ // (1)
 std::size_t operator()(MyInt m) const {
 std::hash<int> hashVal;
 return hashVal(m.val);
 }
};

struct MyEqual{
 bool operator () (const MyInt& fir, const MyInt& sec) const { // (2)
 return fir.val == sec.val;
 }
};

std::ostream& operator << (std::ostream& strm, const MyInt& myIn){ // (3)
 strm << "MyInt(" << myIn.val << ")";
 return strm;
}

int main(){

 std::cout << '\n';

 typedef std::unordered_map<MyInt, int, MyHash, MyEqual> MyIntMap; // (4)

 std::cout << "MyIntMap: ";
 MyIntMap myMap{{MyInt(-2), -2}, {MyInt(-1), -1}, {MyInt(0), 0}, {MyInt(1), 1}};

 for(auto m : myMap) std::cout << '{' << m.first << ", " << m.second << "}";

 std::cout << "\n\n";

}

ハッシュ関数 (1 行目) と equal 関数 (2 行目) を関数オブジェクトとして実装し、便宜上、出力演算子 (3 行目) をオーバーロードしました。 4 行目は、すべてのコンポーネントから新しい型 MyIntMap を作成します MyInt を使用する キーとして。次のスクリーンショットは、インスタンス myMa の出力を示しています p.

ポリシーを実装するには、構成と継承という 2 つの一般的な方法があります。

構成

次のクラス Message コンパイル時にコンポジションを使用して出力デバイスを構成します。

// policyComposition.cpp

#include <iostream>
#include <fstream>
#include <string>

template <typename OutputPolicy>  // (1)
class Message {
 public:
 void write(const std::string& mess) const {
 outPolicy.print(mess);  // (2)
 }
 private:
 OutputPolicy outPolicy; 
};

class WriteToCout {  // (5)
 public:
 void print(const std::string& message) const {
 std::cout << message << '\n';
 }
};

class WriteToFile {  // (6)
 public:
 void print(const std::string& message) const {
 std::ofstream myFile;
 myFile.open("policyComposition.txt");
 myFile << message << '\n';
 }
};


int main() {

 Message<WriteToCout> messageCout;  // (3)
 messageCout.write("Hello world");

 Message<WriteToFile> messageFile;  // (4)
 messageFile.write("Hello world");

}

クラス Message には、ポリシーとしてテンプレート パラメーター OutputPolicy (1 行目) があります。そのメンバ関数 write の呼び出し そのメンバー outPolicy に直接委任します (2行目)。 2 つの異なる Message を作成できます インスタンス (3 行目と 4 行目)。カウントする 1 回の書き込み (5 行目) と、ファイルへの 1 回の書き込み (6 行目)。

スクリーンショットは cout への書き込み操作を示しています およびファイル policyComposition.txt .

継承

継承ベースの実装は、ファイル policyComposition.cpp に基づく複合に非常に似ています。 .主な違いは、複合ベースの実装にはポリシーがあるが、継承ベースの実装はそのポリシーから派生することです。

// policyInheritance.cpp

#include <iostream>
#include <fstream>
#include <string>

template <typename OutputPolicy>  
class Message : private OutputPolicy { // (1) 
 public:
 void write(const std::string& mess) const {
 print(mess);  // (2)
 }
 private:
 using OutputPolicy::print;
};

class WriteToCout {
 protected:
 void print(const std::string& message) const {
 std::cout << message << '\n';
 }
};

class WriteToFile {
 protected:
 void print(const std::string& message) const {
 std::ofstream myFile;
 myFile.open("policyInheritance.txt");
 myFile << message << '\n';
 }
};


int main() {

 Message<WriteToCout> messageCout;
 messageCout.write("Hello world");

 Message<WriteToFile> messageFile;
 messageFile.write("Hello world");

}

クラス Message の以前の実装の代わりに 、これはそのテンプレート パラメーターからプライベートに派生し、プライベートに継承された print を導入します 関数をクラス スコープに追加します。明らかな理由から、プログラムの出力をスキップします。わかった。ポリシーベースの設計を実装するために構成または継承を使用する必要がありますか?

合成または継承

一般的に、私は継承よりも構成を好みます。一般的には、ポリシーベースの設計では継承を考慮する必要があります。

OutputPolicy の場合 が空である場合、いわゆる空の基本クラスの最適化の恩恵を受けることができます。空は OutputPolicy を意味します 非静的データ メンバーはなく、空でない基本クラスもありません。その結果、OutputPolicy Message のサイズには何も追加しません .逆に Message の場合 メンバー OutputPolicy を持っています 、 OutputPolicy Message のサイズに少なくとも 1 バイトを追加します .私の主張は説得力がないように聞こえるかもしれませんが、多くの場合、クラスは複数のポリシーを使用します。

次は?

特性は、ジェネリック型からプロパティを引き出すクラス テンプレートです。それらについては、次の投稿で詳しく書きます。