c++
Up one levelC++パーサーライブラリ、yapc++
旧名ParseaseであるYapc++ version 0.0.1aをリリースしました。i18nの対応もYapc++の機能に含めてしまおうという話でここ数日開発していたのですが、パフォーマンスを落とすかシンプルさを落とすかあるいは両方落とすかの実装しか浮かばなかったので、結局のところi18n対応はYapc++範疇外ということで今回リリースにいたりました。もちろん拡張セットとしてのi18n対応は考慮されていますし、今後のバージョンアップは主にそれの対応だと思われます。ここに書くべき記事は本家のほうへ書くことにしましたので、ここでは変わってその実装に関しての考えかたやテクを書いていきたいと思います。
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/c-30fc30b530fc30e930a430e930ea3001yapc/tbping
pimplイディオムを語る
foo.h:
class foo { ... };
bar.h:
class bar { ... };
があるとして
hoge.h:
#include "foo.h"
#include "bar.h"
class hoge {
private:
foo f;
bar b;
public:
hoge();
foo& get_foo();
bar& get_bar();
};
hoge.cpp:
#include "hoge.h"
hoge::hoge() {}
foo& hoge::get_foo() { return f; }
bar& hoge::get_bar() { return b; }
と書くより
hoge.h:
#include <memory>
class foo;
class bar;
class hoge {
private:
struct impl;
std::auto_ptr<impl> pimpl_;
public:
foo& get_foo();
bar& get_bar();
};
hoge.cpp:
#include "foo.h"
#include "bar.h"
#include "hoge.h"
struct hoge::impl {
foo f;
bar b;
};
hoge::hoge() : pimpl_(new impl) {}
foo& hoge::get_foo() { return pimpl_->f; }
bar& hoge::get_bar() { return pimpl_->b; }
と書くほうがすばらしいよという話。前者の場合はhoge.hをincludeした時点でfoo.hとbar.hまでincludeされてしまって、おまけにfoo.hやbar.hに変更が加えられてしまった場合にhoge.hはもちろんのことそれをincludeしているヘッダファイルまで変更が波及してしまうことになり、結果としてコンパイルが遅くなる。一方後者の場合はhoge.hの他への依存性はほぼゼロである。前方宣言を使うのは当然のことであるが、ここではそれ以上にpimplイディオムが大活躍している。つまりimplのポインタにfooやbarを格納してしまうことにより、それらのサイズや定義を知る必要がないのだ。ゆえにいくらhoge.hをincludeしているヘッダファイルがあろうともfoo.hやbar.hの変更はそれらに波及しえない。これによって変更に対する耐性が保証され、結果としてコンパイルが速くなる。
しかしpimplイディオムのメリットはそれだけではない。いやpimplの本質上、メリットがコンパイルの高速化だけでとどまること自体がそもそもおかしい。挙げだすと切りがないのだが(要するにstackにおくこととheapにおくこととの差によるメリット)、個人的に非常に興味深いのは例外安全に対する驚くべき能力である。たとえばfoo.hが以下のようなアホなコードだったとしよう。
foo.h:
class foo {
public:
...
foo& operator =(const foo& rhs) {
throw not_implemented_error(); // how stupid I am!
}
...
};
このアホクラスを使ってテストコードを書く。
hoge a; hoge b; a = b;
このコードをpimplイディオムでないバージョンのhogeでコンパイルすると a = b の部分で例外が発生する。依存したクラスのおかげで自分のクラスまで非例外安全になるなんてなんてすばらしいのでしょう。しかしpimplイディオムの黒魔術にかかればそんな問題も一発。hoge.hとhoge.cppを以下のように改造する。
hoge.h:
#include <memory>
class foo;
class bar;
class hoge {
private:
struct impl;
std::auto_ptr<impl> pimpl_;
public:
foo& get_foo();
bar& get_bar();
hoge& operator =(const hoge& rhs); // never throw
};
hoge.cpp:
#include "foo.h"
#include "bar.h"
#include "hoge.h"
struct hoge::impl {
foo f;
bar b;
};
hoge::hoge() : pimpl_(new impl) {}
foo& hoge::get_foo() { return pimpl_->f; }
bar& hoge::get_bar() { return pimpl_->b; }
hoge& hoge::operator =(const hoge& rhs) {
if (this != &rhs) {
pimpl_ = rhs.pimpl_;
}
return *this;
}
これで何回 hoge::operator = しようが決して例外が発生することはない。すばらしすぎる。
その他にも new impl_ するタイミングを調整できたり、 struct impl と本来のクラスに実装をわけて構成をわかりやすくしたり、(時と場合によるが)本体のコピーやswapが断然速いなど、pimplにはたくさんのすばらしいメリットがある。ただもちろんそんなにおいしいことばかりでもなく、heapを使う分のパフォーマンス低下は当然考えられる問題だ。しかしメリットとデメリットを計算したとき、最終的にはやはり符号は+になることだろう。
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/pimpl30a430a330aa30e030928a9e308b/tbping
Re:pimplイディオムを語る
Effective C++ 第3版を読みました
会社にあったので流し読み。内容はいたって普通なのですが第2版にはなかったTR1(Technical Report 1)とかいう仕様(ライブラリ)が紹介されていて改めてC++の進化に期待するようになりました。C++0xじゃなくて今のうちにC++xxにしとくべきじゃないなどと思っていたのですが、実は結構ちゃんと進んでいて(当たり前ですが) http://www.open-std.org/ のC++のところを読んでみたりすると実感が持てるわけです。で、そのTR1にはC++98 STLの決定的な欠如であるshared_ptrや、functionオブジェクト、気持ち悪いマジックナンバーを取り除いた汎用bind、regex(これはやりすぎ?と個人的に思うのですがあればあったで便利かな)、tuple(pairなんてうんこですよ)など、これでやっと標準ライブラリとしての体裁が整いそうです。詳細はScott Meyersが http://aristeia.com/EC3E/TR1_info_frames.html に書いています。
で、Effective C++ついでにStandard Template Libraryという本を読みました。本の最初の「STLはオブジェクト指向のライブラリではない」(よく覚えてない)という一文には笑いました。この一文から著者のOOPに対する(というよりOOPを最新技術のようにとりあつかい、それを使えなければプログラマじゃないなどと思っている人に対する)思いとSTLに対する愛が感じられます。内容としても各機能ごとに例が示されていてわかりやすく、以前順列の生成が面倒くさくてしょうがないと書きましたが、それはnext_permutationとprev_permutationで生成できるらしいです。
int a = { 1, 2, 3 };
do {
copy(a, a + 3, ostream_iterator<int>(cout, " ");
cout << endl;
} while (next_permutation(a, a + 3));
// 1 2 3
// 1 3 2
// 2 1 3
// 2 3 1
// 3 1 2
// 3 2 1
STLってやっぱり偏ってるとしかいいようがない。
- Category(s)
- c++
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/Effective_Cpp/tbping
GCC 勉強途中経過
ある改造のためにかれこれ数週間 gcc の変態コードと格闘しているのですが、思ったより時間がかかりそうなので途中経過(今 20% ぐらい)。
その改造というのが、 C++ 大好き子なら一度はやりたいと思うはずの文字列テンプレート引数の対応。一応以下のようなことはできるようになっている。
full specialization:
typedef const char * const cstring;
template <cstring V>
struct S {
static cstring value;
};
template <cstring V> cstring S<V>::value = V;
template <> cstring S<"World">::value = "World :)";
int main() {
std::cout << S<"Hello ">::value << S<"World">::value << std::endl; // Hello World :)
return 0;
}
in-class initialization:
typedef const char * const cstring;
/* in-class initialization of string constant is not yet available in template. */
struct S {
static cstring value = "Hello";
};
int main() {
std::cout << S::value << std::endl; // Hello
return 0;
}
concatenation:
int main() {
/* D like contatenation */
std::cout << "Hello " ~ "World" << std::endl; // Hello World
return 0;
}
まだ未実装だけど以下のような記法も導入したい。
substring:
typedef const char * const cstring;
int main() {
static cstring value = "FooHoge";
std::cout << value[0..3] << " " << value[3..7] << std::endl; // Foo Hoge
}
property:
int main() {
std::cout << "What length of this string?".length << std::endl;
return 0;
}
大体これだけあれば不自由しないはず。
ていうか、 integral で決め打ちになっているので改造がかなり大変、 g++ frontend 。まあそういう仕様だから仕方ないけど。
以下適当なメモ。
- template 内での in-class initialization は、 DECL_EXTERNAL のコントロールが必要
- array_type_node と const_string_type_node を上手く統合する必要がある * 統合というか統合的な比較機構
- ちゃんと mangle したいが as が対応してないのでどうしようもない * とりあえずランダムな数字埋めこんでる
- build_binary_op ではなく fold で演算する必要がある * 後ろに手をいれないといけないかも
- Bjarne Stroustrup が発狂しかねない言語仕様だということは重々承知している
- 正直実装しきる自信がない
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/gcc1/tbping