C ++のファーストクラスの市民のようなメソッド

先日、バグトラッカーに沿って歩くと、gccは興味深いバグに遭遇しました。これは、いくつかのC ++ 11機能を一度に使用します。





このバグを分析し、メソッドをファーストクラスの市民として便利に実装できるようになったと思った



実際に、ウィキペディアは私たちにファーストクラス市民とは何かを説明しています-プログラムのプロセスで作成され、パラメータとして渡され、変数に割り当てられたエンティティは、関数の結果である可能性があります。



準備する



コンパイラーの選択


新鮮なgccやmsvcを手元に持っていなかったため、新鮮なclang-3.1をビルドすることにしました。
mkdir llvm cd llvm svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_31/final ./ cd tools svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_31/final clang cd ../../ mkdir build cd build cmake ../llvm -DCMAKE_INSTALL_PREFIX=/home/pixel/fakeroot -DCMAKE_BUILD_TYPE=Release make -j4 make check-all make install
      
      







libcxxライブラリを選択します


また、新しいコンパイラのすべての機能を使用するためにlibcxxライブラリを構築することも決めました。
 mkdir libcxx cd libcxx svn co http://llvm.org/svn/llvm-project/libcxx/trunk ./ cd ../ mkdir build_libcxx cd build_libcxx CC=clang CXX=clang++ cmake ../libcxx -DCMAKE_INSTALL_PREFIX=/home/pixel/fakeroot -DCMAKE_BUILD_TYPE=Release make -j4 make install
      
      







libcxxアセンブリに関するいくつかの言葉:最後のリリースは私からビルドされたくなかったので、最新バージョンをtrunkから取得することにしました(理解したくなかったので、trunkを取得しました)。 また、libcxxはclangを使用してビルドする必要があります。これには、コンパイラーをclangに置き換えるために、環境変数CCおよびCXXを設定します。 また、何らかの理由で、テストを実行したくありませんでした( make check-libcxx



摘みたてのclangとlibcxxを使用するためのCMakeLists.txtの例


 cmake_minimum_required(VERSION 2.8) project (clang_haxxs) add_definitions(-std=c++11 -nostdinc++) include_directories(/home/pixel/fakeroot/lib/clang/3.1/include) include_directories(/home/pixel/fakeroot/include/c++/v1) link_directories(/home/pixel/fakeroot/lib) add_executable(clang_haxxs main.cpp) set_target_properties(clang_haxxs PROPERTIES LINK_FLAGS -stdlib=libc++)
      
      







したがって、cmakeでは、libcxxをビルドするときと同じ方法で環境変数CCおよびCXXを再定義します。



説明例



したがって、準備プロセスが完了したら、例に進みます。

 #include <iostream> #include <functional> using namespace std; struct FirstClass { FirstClass(): x(0) { } int get_x() const { return x; } function<int ()> f1 = [this]() -> int { cout << "called member function f1..." << endl; ++x; f1 = f2; return 5; }; private: function<int ()> f2 = [this]() -> int { cout << "called member function f2..." << endl; return x; }; int x; }; int main() { FirstClass m; m.f1(); m.f1(); function<int ()> f3 = []() -> int { cout << "called free function f3..." << endl; return 100500; }; m.f1 = f3; m.f1(); return 0; }
      
      





プログラム出力:

called member function f1...

called member function f2...

called free function f3...








実際、C ++ 11を使用しなくても同様の機能を実装できますが、見た目が悪くなります。 コードの可読性への主な貢献は、 非静的メンバーの初期化によって行われます 。C++-03の通常のメソッドと同様のメソッドの宣言と実装を取得します。 その他の機能は、C ++-03およびサードパーティのライブラリによって多少エミュレートされます:boost :: function、boost :: lambda。



浸漬



そのようなオブジェクトで何ができるかをさらに詳しく考えてみましょう:

静的および非静的メソッドのエミュレーション


ここではすべてがシンプルです。このメソッドにアクセスできる場合、メソッドは静的ではありません。 したがって、クラス本体でラムダ関数を定義する場合、これをキャプチャリストに追加します。 これで、ラムダ関数から、クラスのすべてのメンバー(プライベートなメンバーを含む)にアクセスできます。



ここには特異性があります。実際、静的関数の概念はここではまったく正しく使用されていません。最初にC ++で作成されたオブジェクトなしで呼び出すことができる関数として定義されているため、関数に到達するためにオブジェクトを作成する必要があります。



クラス外のメソッドの設定


非静的関数を定義する方法を考え出しましたが、クラス外でこれを行う方法を理解するために残っています。非常に簡単です。この関数が関連付けられているオブジェクトをキャプチャリストに転送する必要があります。
  function<int ()> f4 = [&m]() ->int { cout << "called free function f4 with capture list..." << endl; return m.get_x() + 1; }; m.f1 = f4; m.f1();
      
      







ここでは、キャプチャリスト内のオブジェクトへの参照を渡すときに注意する必要があります。関数を定義してオブジェクトにバインドする操作のタイミングがとられるため、次の間違いを犯す可能性があります。

「キャプチャリストで指定された間違ったオブジェクトにバインドします。」



また、ここにある別の制限、クラス宣言の外部に関数をアタッチすると、プライベートクラス変数へのアクセスが失われます。
  function<int ()> err = [&m]() ->int { cout << "called free function err with capture list..." << endl; return mx + 1; };
      
      





同時に、コンパイラは次のことを誓います: /usr/src/projects/clang/usage/main.cpp:64:12: error: 'x' is a private member of 'FirstClass'







メソッドのオーバーライドの禁止


ここではすべてが単純です。メソッドはクラスの通常のメンバーであり、その説明にconstを追加しているため、必要なものを取得するだけです。
  struct FirstClassConst { const function <int()> f1 = []() -> int { return 1; }; }; FirstClassConst mc; mc.f1 = f3;
      
      





コンパイラは私たちを/usr/src/projects/clang/usage/main.cpp:70:8: error: no viable overloaded '='

mc.f1 = f3;

~~~~~ ^ ~~




/usr/src/projects/clang/usage/main.cpp:70:8: error: no viable overloaded '='

mc.f1 = f3;

~~~~~ ^ ~~




: /usr/src/projects/clang/usage/main.cpp:70:8: error: no viable overloaded '='

mc.f1 = f3;

~~~~~ ^ ~~




/usr/src/projects/clang/usage/main.cpp:70:8: error: no viable overloaded '='

mc.f1 = f3;

~~~~~ ^ ~~








constメソッドの欠如


正直なC ++メソッドには、メソッドがクラスのメンバーを変更しないことを決定する機能があり、定数オブジェクトに適用できます。そのようなメソッドはconst修飾子でマークされます。 例では、これはget_xメソッドです。

メソッドをオブジェクトとして実装すると、この可能性はなくなり、代わりに定数オブジェクトのメンバーを変更できます。
  struct MutableFirstClass { int x; MutableFirstClass(): x(0){} int nonConstMethod() { ++x; return x; } function <int()> f1 = [this]() -> int { this->x = 100500; return x; }; }; const MutableFirstClass mm; mm.f1(); //mm.nonConstMethod();
      
      





最後の呼び出しのコメントを/usr/src/projects/clang/usage/main.cpp:93:2: error: member function 'nonConstMethod' not viable: 'this' argument has type 'const MutableFirstClass', but function is not marked const

mm.nonConstMethod();

^~




と、コンパイラは次のように誓います: /usr/src/projects/clang/usage/main.cpp:93:2: error: member function 'nonConstMethod' not viable: 'this' argument has type 'const MutableFirstClass', but function is not marked const

mm.nonConstMethod();

^~




/usr/src/projects/clang/usage/main.cpp:93:2: error: member function 'nonConstMethod' not viable: 'this' argument has type 'const MutableFirstClass', but function is not marked const

mm.nonConstMethod();

^~






ほとんどの場合、次の一連のアクションが発生します。

非静的メンバーの初期化は構文上のシュガーにすぎないため、 キャプチャリストでのこの キャプチャはコンストラクタで行われ、コンストラクタではこれはMutableFirstClass * const型であるため、変数の値を変更できます。



私が覚えている限り、定数オブジェクトでは、メンバー-UB(可変修飾子でマークされたメンバーを除く)の値を変更するため、定数オブジェクトでこのようなメソッドを慎重に使用する必要があります。



次は何ですか



実際、この機能を使用する可能性は非常に議論の余地があります-一方では、Pythonのようにデコレータパターンを簡単に実装できます。これは長所の1つです。GoFのような多数の継承クラスの退屈な実装を取り除きます。 各オブジェクトを個別に装飾することもできます。たとえば、オブジェクトを入力として受け取り、メソッドの1つに装飾子を追加する装飾関数を作成できます。 これは、GoFで説明されているこのパターンを使用して行うことはできません。



一方、定数オブジェクトのメンバーに対する保護の欠如は非常に重大な欠点であるため、このソリューションを適用する前に慎重に検討する必要があります。



また、メモリ消費量が増加します。libcxxの実装では、各メソッドは16バイトを使用するため、メソッドの数が増えると、より大胆なオブジェクトが増えます。



また、時間測定を行い、そのようなメソッドの呼び出し速度をネイティブC ++メソッドと比較する必要があります(速度を仮想メソッドと比較できます)。



All Articles