C++ コア ガイドライン:型消去

ジェネリック プログラミングのコア ガイドラインのルール「T.5:ジェネリックと OO の手法を組み合わせて、コストではなく強みを増幅する」では、型消去を例として使用しています。 .タイプ消去?本当!もちろん、この高度なテンプレート手法を説明するには 2 回の投稿が必要です。

まず第一に:型消去とはどういう意味ですか?

  • タイプ消去: Type Erasure を使用すると、単一の汎用インターフェースを通じてさまざまな具象型を使用できます。

もちろん、すでに C++ や C でかなり頻繁に型消去を使用しています。型消去の C っぽい方法は void ポインターです。型消去の C++ 風の方法はオブジェクト指向です。 void ポインターから始めましょう。

ボイド ポインター

std::qsort の宣言を詳しく見てみましょう :

void qsort(void *ptr, std::size_t count, std::size_t size, cmp);

と:

int cmp(const void *a, const void *b);

比較関数 cmp を返す必要があります

  • 負の整数:最初の引数が 2 番目の引数より小さい
  • ゼロ:両方の引数が等しい
  • 正の整数:最初の引数が 2 番目の引数より大きい

void ポインターのおかげで、std::qsort

std::vector<int>, をソートしたいかもしれません しかし、C文字列にはコンパレータを使用しました。型情報が削除されているため、コンパイラはこのエラーをキャッチできません。未定義の動作で終了します。

C++ では、次のように改善できます:

オブジェクト指向

これは簡単な例であり、さらなるバリエーションの出発点となります。

// typeErasureOO.cpp

#include <iostream>
#include <string>
#include <vector>

struct BaseClass{ // (2)
 virtual std::string getName() const = 0;
};

struct Bar: BaseClass{ // (4)
 std::string getName() const override {
 return "Bar";
 }
};

struct Foo: BaseClass{ // (4)
 std::string getName() const override{
 return "Foo";
 }
};

void printName(std::vector<const BaseClass*> vec){ // (3)
 for (auto v: vec) std::cout << v->getName() << std::endl;
}


int main(){
 
 std::cout << std::endl;
 
 Foo foo;
 Bar bar; 
 
 std::vector<const BaseClass*> vec{&foo, &bar}; // (1)
 
 printName(vec);
 
 std::cout << std::endl;

}

std::vector<const Base*> (1) 定数 BaseClasses へのポインタを持つ . BaseClass (3)で使用される抽象基底クラスです。 FooBar (4) は具象クラスです。

プログラムの出力はそれほどスリリングではありません.

もっと正式に言うと。 Foo および Bar BaseClass のインターフェースを実装する したがって、BaseClass. の代わりに使用できます。 この原則は Liskov 置換原則と呼ばれ、OO の型消去です。

オブジェクト指向プログラミングでは、インターフェイスを実装します。 Python などの動的型付け言語では、インターフェースには関心がなく、振る舞いに関心があります。

テンプレート

ちょっと寄り道させてください。

Python では、正式なインターフェイスではなく、動作に注意を払います。このアイデアはダックタイピングとしてよく知られています。簡単に言うと、表現はジェームズ・ウィットコム・ライリーズの詩に戻ります:ここにあります:

「アヒルのように歩き、アヒルのように泳ぎ、アヒルのように鳴く鳥を見るとき、私はその鳥をアヒルと呼びます。」

どういう意味ですか?関数 acceptOnlyDucks を想像してみてください アヒルのみを引数として受け入れます。 C++ などの静的に型付けされた言語では、Duck から派生したすべての型 関数を呼び出すために使用できます。 Python では、Duck のように動作するすべての型 を使用して関数を呼び出すことができます。より具体的にするために。鳥が Duck のように振る舞う場合 Duck です . Python では、この動作が非常に優れていることを説明するためによく使われることわざがあります。

許可を求めるのではなく、許しを求めてください。

Duck の場合、これは関数 acceptsOnlyDucks を呼び出すことを意味します。 鳥と一緒に最高のものを願っています。何か問題が発生した場合は、except 句で例外をキャッチします。多くの場合、この戦略は Python で非常にうまく機能し、非常に高速です。

さて、これで私の回り道は終わりです。なぜこの C++ の投稿でダック タイピングについて書いたのか疑問に思うかもしれません。理由は至って簡単です。テンプレートのおかげで、C++ でダック タイピングができます。ダック タイピングを OO と組み合わせると、タイプ セーフになります。

std::function ポリモーフィック関数ラッパーは、C++ での型消去の良い例です。

std::関数

std::function 関数のように動作するすべてを受け入れることができます。より正確には。これは、関数、関数オブジェクト、std::bind によって作成された関数オブジェクトなど、任意の呼び出し可能オブジェクトにすることができます 、または単なるラムダ関数。

// callable.cpp

#include <cmath>
#include <functional>
#include <iostream>
#include <map>

double add(double a, double b){
 return a + b;
}

struct Sub{
 double operator()(double a, double b){
 return a - b;
 }
};

double multThree(double a, double b, double c){
 return a * b * c;
}

int main(){
 
 using namespace std::placeholders;

 std::cout << std::endl;

 std::map<const char , std::function<double(double, double)>> dispTable{ // (1)
 {'+', add }, // (2)
 {'-', Sub() }, // (3)
 {'*', std::bind(multThree, 1, _1, _2) }, // (4)
 {'/',[](double a, double b){ return a / b; }}}; // (5)

 std::cout << "3.5 + 4.5 = " << dispTable['+'](3.5, 4.5) << std::endl;
 std::cout << "3.5 - 4.5 = " << dispTable['-'](3.5, 4.5) << std::endl;
 std::cout << "3.5 * 4.5 = " << dispTable['*'](3.5, 4.5) << std::endl;
 std::cout << "3.5 / 4.5 = " << dispTable['/'](3.5, 4.5) << std::endl;

 std::cout << std::endl;

}

この例では、文字を callable にマップするディスパッチ テーブル (1) を使用します。 callable は、関数 (1)、関数オブジェクト (2)、std::bind によって作成された関数オブジェクトのいずれかです。 (3)、またはラムダ関数。 std::functionのポイント つまり、すべての異なる関数タイプを受け入れるということです そしてそれらのタイプを消去します。 std::function callable から 2 つの double's を取る必要があります double: std::function<double(double, double)>. を返します

例を完成させるための出力は次のとおりです。

次の投稿でテンプレートを使用した型消去について詳しく書く前に、型消去を実装するための 3 つの手法をまとめておきましょう。

void ポインター、オブジェクト指向、またはテンプレートを使用して型消去を実装できます。テンプレートを使用した実装のみが型安全であり、型階層を必要としません。テンプレートに欠けている詳細が続きます。

次は?

テンプレートを使用した型消去がどのように実装されているか知りたいと思いますか?もちろん、次の投稿までお待ちください。