C++ポケットリファレンスの打ち上げをしました。
C++ポケットリファレンスが5月に発売されまして、いろいろとあってこのタイミングで著者とレビューワーで集まって打ち上げを行いました。
東京でシャブシャブとすき焼きを食べ放題しながらなんやかんやC++やらサラダ油醤油ご飯やらの話をしたのです。
こうやって関係者で一緒に集まって発売を祝えたのは嬉しいですね。
打ち上げを企画してくださったあんどちんさんありがとうございます。
それから、著者全員が集まるのはこれが初めての機会なので、サインをもらってきました!!
著者6人全員のサインが入ったC++ポケットリファレンスはおそらくこの1冊が初めてです!!(ドヤッ
アキラさんのブログにある通り、また成果を出して、そして打ち上げできたらいいな。
現在のディレクトリでGit Extensionsを開くバッチ
@echo off if "%1" == "" ( set target_dir=%CD% ) else ( set target_dir=%~f1 ) echo open GitExtensions : %target_dir% start "" /B "C:\Program Files (x86)\GitExtensions\GitExtensions.exe" browse %target_dir%
ヘッダオンリーで簡単なタスクライブラリhwm.taskを作ってみた。
勉強会での発表
先日Sapporo.cppとCLR/Hの合同勉強会を開催し、
僕はC++11のスレッドについて紹介をしました。
この発表では、C++11で導入されたスレッドライブラリの中から、
- thread
- mutex/lock
- future/promise
- condition_variable
について、それぞれがどんなものかを紹介し、また、それらの機能を使用して簡単なタスクライブラリを作ったという話をしました。
実際のセッションでは、発表時間が足りなくなってしまい、ちゃんとこのhwm.taskを紹介できなかったので、ちょっと残念だったのと、
あと、当日発表したときは未完成なところや微妙なところがもあり、それからもう少し手を加えたりしたので、改めてこのブログで紹介したいと思います。
hwm.task
GitHub - hotwatermorning/hwm.task: minimal task library
- git cloneしてきた後、libs/exampls/SConstructファイルのgcc(4.8以降とかがいいのかも)の設定を変更してsconsで実行すると、libs/examples/bin/task以下にサンプルコードをコンパイルしたバイナリが生成されます。
- 発表時と異なり、boostは使わなくなりました。
- C++11対応コンパイラであれば、hwm.taskはヘッダオンリーで使用できます。
概要
さて、このhwm.taskですが、次のように使用できます。
#include <iostream> #include <hwm/task/task_queue.hpp> int main() { //! タスクキュー //! キューに積まれた関数や関数オブジェクトを別スレッドで随時取り出して実行する。 //! 実行するスレッドの数をコンストラクタで指定する。 hwm::task_queue tq(std::thread::hardware_concurrency()); std::future<int> f = tq.enqueue( //! タスクキュー内のスレッドで実行する関数 //! 関数の戻り値(あるいは例外)が、内部のpromiseオブジェクトに設定される。 [](int x1, int x2) -> int { std::cout << (x1 + x2) << std::endl; return x1 + x2; }, //! 関数に渡す引数 10, 20 ); //! タスクの実行結果は、enqueue()から返るfutureオブジェクトを通じて取得できる。 std::cout << "calculated value : " << f.get() << std::endl; }
解説
hwm::task_queueクラスが、タスクを複数個保持し、適宜取り出して実行するためのタスクキューの実装です。
別スレッドで実行されて欲しい何らかの処理をtask_queueクラスに追加するには、task_queue::equeue()メンバ関数を使用します。
enqueue()メンバ関数は、std::threadクラスなどと同じように、関数のように呼び出し可能な何かと、その何かに適用する引数を渡せるようになっています。
(注:enqueue()メンバ関数は、発表時のスライドではenqueue_syncという名前でした。)
namespace hwm { struct task_queue { //... template<class F, class... Args> std::future<FにArgsを適用させた戻り値の型> enqueue(F&& f, Args&&... args); //... }; }
別スレッドで実行されて欲しい関数や関数オブジェクトと、それに適用される引数を渡してenqueue()メンバ関数を呼び出すと、それらを元に内部でタスクを生成し、そのタスクがタスクキューに追加されます。
追加されたタスクは、タスクキューが管理するいずれかのスレッドによってキューから取り出され、実行されます。
取り出されたタスクの実行結果(渡した関数の戻り値、もしくは送出される例外)は、enqueue()メンバ関数の返り値であるstd::future<>オブジェクトを通じて受け取れます。
task_queueは、各タスクを適宜別スレッドで実行しますが、いくつのスレッドを立ち上げるかは、task_queueクラスのコンストラクタで指定できます。
また、コンストラクタでは、キューに保持できるタスク数の上限も設定できます。
この数を超えてタスクが追加されようとした場合、タスクキューのいずれかのスレッドによってタスクがキューから取り出されるまで、enqueue()メンバ関数の呼び出しはブロックされます。
(各タスクは、キューから取り出されてから実行されるため、enqueue()メンバ関数のブロックが解除されたからといって、その時にキューから取り出されたタスクの実行が終了しているかどうかは不定です。)
namespace hwm { struct task_queue { //! @brief コンストラクタ //! @detail 引数に指定された値だけスレッドを起動する //! @param thread_limit [in] 起動する引数の数 //! @param queue_limit [in] キューに保持できるタスク数の限界 task_queue(size_t thread_limit, size_t queue_limit = ((std::numeric_limits<size_t>::max)())); //... }; }
また、enqueue()メンバ関数の呼び出しはマルチスレッドセーフです。
task_queueとは独立している任意のスレッドから、任意のタイミングでenqueue()メンバ関数を呼び出せます。
hwm::task_queue tq; void thread_process1() { for(int i = 0; i < 100; ++i) { tq.enqueue( /*なんらかのタスクを追加*/ ); } } void thread_process2() { for(int i = 0; i < 100; ++i) { tq.enqueue( /*なんらかのタスクを追加*/ ); } }
task_queueにいくつかタスクが積まれている場合、wait()メンバ関数を呼び出して、タスクの終了を待機できます。
void add_many_tasks(hwm::task_queue &tq) { for(int i = 0; i < 100; ++i) { tq.enqueue([]() -> void { /*なんらかの処理*/ }); } } void foo() { hwm::task_queue tq; add_many_tasks(tq); // なんらかの処理など // すべてのタスクが取り出され、実行が終了するのを待機する。 tq.wait(); }
同様に、指定時間だけ終了待機をするwait_for()、指定時刻まで終了待機をするwait_until()があります。
(これら待機用の関数は、enqueue()によるタスクの追加をブロックしないため、wait()メンバ関数を呼び出している最中にどんどんタスクが追加されたりすると、いつまでたっても処理が戻りません)
task_queueクラスは、デフォルトではデストラクト時にwait()を呼び出しタスクキューに積まれたタスクがすべて実行され完了するのを待機します。
この挙動を変更し、デストラクト時にwait()を呼び出さず、積まれているタスクをすべて破棄したい場合はtask_queue.set_wait_before_destructed(false)を呼び出します。
wait()しないように設定した場合、実行されなかったタスクは、内部で保持しているpromise対して、エラーコードstd::future_errc::broken_promiseを指定したstd::future_error例外を設定します。
(内部で保持しているpromiseが、なにも値が設定されないまま破棄されたため。)
そのため、対応するfutureのget()メンバ関数を呼び出すと、その例外が再送出され、破棄されたことを検出できます。
int main() { std::cout << ">>> don't wait before destructed" << std::endl; std::future<int> f_dont_wait; { hwm::task_queue tq(1, 2); tq.enqueue([]() { std::this_thread::sleep_for(std::chrono::seconds(1)); }); f_dont_wait = tq.enqueue( // この関数はほぼ確実に実行されない。 [](int a, int b) { return a + b; }, 10, 20); tq.set_wait_before_destructed(false); } std::cout << "<<< don't wait before destructed" << std::endl; try { std::cout << "10 + 20 = " << f_dont_wait.get() << std::endl; } catch(std::future_error &e) { // 実行されるより前にtask_queueがデストラクトされるはず。なので例外が設定されているはず。 std::cout << e.what() << std::endl; //Broken promiseとかが出力されるはず。 } }
こんな感じのtask_queueの使用例を示すサンプルコードは、examplesディレクトリ内にあります。
対応環境
とりあえずGCC 4.8.2ではコンパイルできて動いているのを確認しています。
また、Clangでは試していないです。
C++11の機能としてVariadic Templatesを使用していますし、スレッドライブラリも標準のstd::threadを使用しているため、Visual Studio 2012では使えません。
Visual Studio2012に対応する場合、標準の<thread>, <future>などはbuggyで使えないため、boostのものを使用することになります。可変長引数も、Boost.Preprocessorを使用して対応することになります。
ちょっと面倒でまだちゃんとできてないため、公開はまだ先になります。
最後に
hwm.taskは勉強会での発表のためだけではなく、最近自分でも必要になったため作りました。
ただ作ったとはいいつつ、バグや設計上の工夫点はまだまだあるかもしれません。
(たとえば、task_queueクラスのキューを担うコンテナをテンプレート引数で指定できたり、Allocatorを指定できたりとかですかねー。必要性を感じたらやるかも。)
あと、テストとかドキュメントとかまだまだです。
とりあえず、折角作ったのでちょくちょく使っていきたいです。
右クリックのメニューに出てくるGitExtensionsのコマンドを削除する方法。
GitExtensionsを入れたら、エクスプローラーで右クリックしたら出てくるメニュー(コンテキストメニュー)にGitExtensionsのコマンドがいろいろ表示されるようになりました。
不必要なのでこれを消したいという場合は、
cd c:\Program Files (x86)\GitExtensions or where you installed GitExtensions
regsvr32 /u GitExtensionsShellEx32.dll
regsvr32 /u GitExtensionsShellEx64.dll if you have 64bit Windows
のようにします。
5/20現在でissueに提案されてるのでそのうちGitExtensions上で設定できるようになるかもですね。
Add "Remove Shell Extensions" in Settings Menu · Issue #1821 · gitextensions/gitextensions · GitHub
C++にfinally?
昨晩ついったーでもつぶやいたのですが、
C++とfinally - 株式会社CFlatの明後日スタイルのブログ
このブログ記事を読んだ感想は、C++にfinallyは特にいらないんじゃないかなー、でした。
1. リソース管理するならRAII使うほうがいいと思う
関数の実行時にどのような例外が発生しても、プログラムを正しく矛盾のない状態に保つことを例外安全といいます。
例外安全 - プログラミング言語 D (日本語訳)
例外処理 - Wikipedia
Exceptional C++―47のクイズ形式によるプログラム問題と解法 (C++ in‐Depth Series)
- 作者: ハーブサッター,浜田光之,Harb Sutter,浜田真理
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2000/11
- メディア: 単行本
- 購入: 9人 クリック: 134回
- この商品を含むブログ (63件) を見る
先のブログでは、finallyの利点として、newで確保したメモリを例外発生時にも例外が発生しない時にも正しく解放するという例外安全の処理が、finallyを使用するとすっきりと書けるという風に述べられています。
C++ではリソースを管理し、例外安全を実現するためにRAIIというIdiomを使用します。
RAIIとはResource Acquisition Is Initializationの頭文字で、オブジェクトの初期化の際にリソースを確保し、デストラクタでそのリソースを解放する仕組みです。
先のブログで使用しているstd::unique_ptrは、メモリというリソースの管理にRAIIを用いた、スマートポインタと呼ばれる種類のクラスです。
スマートポインタのデストラクタがメモリを自動で解放するため、例外発生時にも例外が発生しない時にもリソース管理と例外安全の処理を明示的に書く必要がなくなります。
先のブログで例外安全の処理のために、finallyの代替手段としてRAII(スマートポインタ)を使った例が載せられていますが、
(コード引用)
try { std::unique_ptr<int[]> p(new int[10]) ; std::cout << "例外を投げるかもしれない処理" << std::endl ; if (condition) return ; if (need_goto) goto LABEL ; ... } catch (std::exception e) { std::cout << "Caught a std::exception!" << std::endl ; } catch (...) { std::cout << "Caught an unexpected exception!" << std::endl ; throw ; } ... LABEL : ...
finallyを使ったコードよりもすっきり書けているのではないでしょうか。
メモリ以外でRAIIが有効な場面としては、Mutexの管理などがあります。これはMutexの所有権をリソースとして、その管理にRAIIを用いるものです。
#include <iostream> #include <mutex> //標準スレッドはC++11から。C++03環境ではBoost.Threadを使用する #include <thread> //標準スレッドはC++11から。C++03環境ではBoost.Threadを使用する std::mutex mtx; //Mutexを表すクラス。Mutexの所有権を取得するlock()メンバ関数, 所有権を解放するunlock()メンバ関数を持つ int count = 0; void foo() { for(int i = 0; i < 100; ++i) { std::lock_guard<std::mutex> lock(mtx); //このスコープ内だけMutexを取得する count += 1; } //for文の先頭に戻る時やfor文を抜ける際にはMutexを解放する } void bar() { for(int i = 0; i < 100; ++i) { std::lock_guard<std::mutex> lock(mtx); //このスコープ内だけMutexを取得する count += 1; } //for文の先頭に戻る時やfor文を抜ける際にはMutexを解放する } int main() { std::thread th1(foo); //foo関数を別スレッドで起動 std::thread th2(bar); //bar関数を別スレッドで起動 th1.join(); //スレッドの終了を待機 th2.join(); //スレッドの終了を待機 std::cout << count << std::endl; }
ここではRAIIによるロックの管理にstd::lock_guardクラス(クラステンプレート)を使用しています。
std::lock_guardクラスはテンプレート引数に、BasicLockableコンセプトを満たすクラス、すなわちlock()メンバ関数とunlock()メンバ関数を持つクラスを指定します。
std::lock_guardクラスのオブジェクトを初期化する際に、テンプレート引数で指定したクラスのオブジェクトをコンストラクタに渡すと、コンストラクタ内で受け取ったオブジェクトのlock()メンバ関数を呼び出してMutexの所有権を確保し、逆にstd::lock_guardクラスのオブジェクトが破棄される際には、デストラクタでunlock()メンバ関数を呼び出してMutexの所有権を解放します。
このようにRAIIを用いることで、「Mutexの所有権を適宜取得し過不足なく解放する」という煩雑な処理を、単にオブジェクトの寿命として管理できるため、ひとつのMutexの所有権を、明示的に手続き的に取得と解放を対応付けて管理する必要がなくなります。
またファイルストリームもRAIIの良い例です。std::fstreamクラスのオブジェクトを初期化する際にコンストラクタにファイル名を渡すと、内部でそのファイルを開き、オブジェクトが破棄される際にはファイルが自動で閉じられる様になっています。
そして、RAIIはtry-catch句とは独立した仕組みであるため、例外と関係なく、以下のような適当な場所で関数から戻りたいという状況でも使用できます。
void foo(int condition) { std::unique_ptr<SomeClass> p(new SomeClass); if(condition == CONDITION_A) { doSomething(p); return; } else if(condition == CONDITION_B || condtion == CONDITION_C) { bool result = doSomethingDifferent(p); if(result) { return; } doSomethingDifferentDifferent(result); } else { doSomethingDifferentDifferentDifferent(p); } }
2. スコープ抜けるときになにか処理させたいならScopeExit的な仕組み使うほうがいいと思う
ScopeExitはスコープを抜ける際に指定した処理を行うための仕組みです。
D言語では言語機能に含まれ、C++ではBoost.ScopeExitなどを用いて実現できます(多少不恰好ですが)
#include <iostream> #include <boost/scope_exit.hpp> void foo() { BOOST_SCOPE_EXIT(void) { std::cout << "try{}が終わろうともreturnされようともthrowされようとも必ず実行する処理" << std::endl; } BOOST_SCOPE_EXIT_END try { std::cout << "例外を投げるかもしれない処理" << std::endl; return; } catch (...) { std::cout << "例外が発生した!" << std::endl; throw; } } int main() { foo(); }
finallyとScopeExitを比較すると、ScopeExitでは、try-catch句よりも先に後処理を書く必要があります。
finallyを使用した場合は、(例外が投げられようと、投げられまいと、)最終的に行いたい処理を、関数の最後にまとめかけるため、処理の流れが直感的になるというのが利点かもしれません。
しかし、必ずしも最後に書くことが最良ではない状況もあると思います。先のブログで書かれていたように、「間の処理が長くなると初期化処理と後始末がコード上で離れていき、わけがわからなくなる心配が」あるためです。
ScopeExitはスコープからから抜ける際に処理が行われるという意図が明確であるため、関数内で行われた何らかの前処理と組み合わせて使用することで、前処理と対応する後処理を明確に表現することができます。
例外安全 - プログラミング言語 D (日本語訳)
こちらのページの言葉を借りれば、
密接に関連した処理は一カ所にまとまっているべきです。
というわけです。
また、ScopeExitはfinallyよりも汎用的です。
try句あるいはtry-catch句に付随して使用しなければならないfinallyに対し、ScopeExitはRAIIの場合と同じようにtry句やtry-catch句とは独立した仕組みであるため、それら例外処理に関する機能とは関係なく使用できます。
例えば以下のように、適当な場所で関数から戻りつつ、関数から戻る際に行いたい処理を明示的に表すためにScopeExitを使用することもできます。
void foo(int condition) { int result = 0; //! 関数を抜ける際にログを吐くようにする BOOST_SCOPE_EXIT((condition)(&result)) { time_t const now = time(0); doLogging(now, condition, result); } BOOST_SCOPE_EXIT_END if(condition == CONDITION_A) { result = doSomething(condition); return; } else if(condition == CONDITION_B || condition == CONDITION_C) { result = doSomethingDifferent(condition); if(result != 0) { return; } result = doSomethingDifferentDifferent(condition); } else { result = doSomethingDifferentDifferentDifferent(condition); } } // 例外に関係なく、関数から抜ける際にログが吐き出される
まとめ
finallyは、例外安全を実現するためとスコープを抜ける際に必ず行う処理のためと両方の場面で使用されますが、リソース管理にはRAIIを、スコープを抜ける際の処理にはScopeExitを使用すると、コード上で意図がより明確になり、finallyよりもわかりやすいように思います。
このように、C++ではリソースの管理や例外安全性、スコープを抜ける際の処理について、finallyとは別の方法で、よりコードの意図を明確にする仕組みがあるため、finallyの必要性は低いんじゃないかなーと思いました。
CやC++でワイド文字列パスのファイルを開く
0.CやC++でワイド文字列パスのファイルを開き、バイナリ読み書きしたい。
1._wfopenを使用する。
Download Visual Studio 2005 Retired documentation from Official Microsoft Download Center
#include <cstdio> #include <vector> bool open_with_wfopen(wchar_t const *filepath) { FILE *fp = _wfopen(filepath, L"rb"); if(!fp) { return false; } std::vector<char> tmp(256); fread(&tmp[0], sizeof(char), tmp.size() - 1, fp); std::cout << &tmp[0] << "\n\n" << std::endl; fclose(fp); return true; }
2.ファイルハンドルを使用する。
c - Is there a Windows equivalent to fdopen for HANDLEs? - Stack Overflow
Windows previous versions documentation | Microsoft Docs
Download Visual Studio 2005 Retired documentation from Official Microsoft Download Center
Download Visual Studio 2005 Retired documentation from Official Microsoft Download Center
#include <io.h> #include <fcntl.h> #include <cstdio> #include <vector> #include <windows.h> bool open_with_handle_to_fopen(wchar_t const *filepath) { HANDLE file_handle = CreateFileW( filepath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS, 0 ); if(file_handle == INVALID_HANDLE_VALUE) { return false; } int crt_file_handle = _open_osfhandle(reinterpret_cast<intptr_t>(file_handle), _O_RDONLY|_O_BINARY); if(crt_file_handle == -1) { CloseHandle(file_handle); return false; } file_handle = NULL; //now use _close() instead CloseHandle() FILE *fp = _fdopen(crt_file_handle, "rb"); if(!fp) { _close(crt_file_handle); return false; } crt_file_handle = -1; //now use fclose() instead _close() std::vector<char> tmp(256); fread(&tmp[0], sizeof(char), tmp.size() - 1, fp); std::cout << &tmp[0] << "\n\n" << std::endl; fclose(fp); return true; }
3.fstreamを使用する。
ifstream::ifstream - C++ Reference
c++ - How to open an std::fstream (ofstream or ifstream) with a unicode filename? - Stack Overflow
#include <fstream> #include <vector> bool open_with_fstream(wchar_t const *filepath) { std::ifstream is(filepath, std::ios_base::in|std::ios_base::binary); if(!is) { return false; } std::vector<char> tmp(256); is.read(&tmp[0], sizeof(char) * (tmp.size()-1)); std::cout << &tmp[0] << "\n\n" << std::endl; return true; }
4. Boost.Iostreamsを使用する。
#include <vector> #include <boost/iostreams/stream.hpp> #include <boost/iostreams/device/file_descriptor.hpp> bool open_with_boost_iostream(wchar_t const *filepath) { namespace ios = boost::iostreams; namespace fs = boost::filesystem; ios::stream<ios::file_descriptor_source> is(fs::path(filepath), std::ios_base::in|std::ios_base::binary); if(!is) { return false; } std::vector<char> tmp(256); is.read(&tmp[0], sizeof(char) * (tmp.size()-1)); std::cout << &tmp[0] << "\n\n" << std::endl; return true; }
5.Boost.Filesystemを使用する。
#include <vector> #include <boost/filesystem/fstream.hpp> bool open_with_boost_filesystem(wchar_t const *filepath) { namespace fs = boost::filesystem; fs::path const bpath = filepath; fs::ifstream is(bpath); if(!is) { return false; } std::vector<char> tmp(256); is.read(&tmp[0], sizeof(char) * (tmp.size()-1) ); std::cout << &tmp[0] << "\n\n" << std::endl; return true; }