Personal tools
You are here: Home ブログ matsuyama Categories c++
Document Actions

c++

Up one level

Document Actions

C++パーサーライブラリ、yapc++

Yapc++

旧名ParseaseであるYapc++ version 0.0.1aをリリースしました。i18nの対応もYapc++の機能に含めてしまおうという話でここ数日開発していたのですが、パフォーマンスを落とすかシンプルさを落とすかあるいは両方落とすかの実装しか浮かばなかったので、結局のところi18n対応はYapc++範疇外ということで今回リリースにいたりました。もちろん拡張セットとしてのi18n対応は考慮されていますし、今後のバージョンアップは主にそれの対応だと思われます。ここに書くべき記事は本家のほうへ書くことにしましたので、ここでは変わってその実装に関しての考えかたやテクを書いていきたいと思います。

Category(s)
c++
program
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を使う分のパフォーマンス低下は当然考えられる問題だ。しかしメリットとデメリットを計算したとき、最終的にはやはり符号は+になることだろう。

Category(s)
c++
program
The URL to Trackback this entry is:
http://dev.ariel-networks.com/Members/matsuyama/pimpl30a430a330aa30e030928a9e308b/tbping

Re:pimplイディオムを語る

Posted by valp at 2008-03-19 00:00
auto_ptrではなくてshared_ptr(boostあるいは最近だとstd::tr1)にしないとoperator =はまずいです。標準のauto_ptrは参照カウントなどではないので、現在の規格に合致した処理系ではpimpl_ = rhs.pimpl_;が思った通りにならないはずです。

Re:pimplイディオムを語る

Posted by M太郎 at 2009-04-24 09:57
>思った通りにならないはずです。
pimpl_ = rhs.pimpl_;

delete pimpl_;
pimple_ = rhs.pimple_;
ってことになってOKじゃないですか?

Re:pimplイディオムを語る

Posted by Anonymous User at 2009-05-15 13:48
おそらく、どちらのauto_ptrが最終的にポインタをdeleteするのか?って問題のことをおっしゃってるのでは無いでしょうか?
auto_ptr a;
{
auto_ptr b;
b = a;
//ここでbがデストラクタされる、ポインタ先を解放する
}
//ここの時点で、aはすでに解放された不正なポインタ指してる。

ってな具合で。

Re:pimplイディオムを語る

Posted by Fumi at 2010-01-02 16:06
>思った通りにならないはずです。
pimpl_ = rhs.pimpl_;

pimpl_.reset(rhs.pimpl_.release());
だから

auto_ptr a(new Hoge());
{
  auto_ptr b;
  b = a; // ここでaはbに所有権を委譲し,aは0ポインタを指す.
  // ここでbのデストラクタが呼び出され,ポインタ先を解放する.
}
// ここの時点で,aは0ポインタ指している.


ってな具合でここの動作に関しては問題ないと思います.

Re:pimplイディオムを語る

Posted by hirose at 2010-01-08 00:29
pimpleイディオムでauto_ptrを使うこと自体が拙いです。
代入してpimpleの所有権が移ってしまうと、元のオブジェクトが持ってたpimpleはどこへ?

boostを使ってよいのなら、pimpleにはboost::scoped_ptr を使うべきです。
#ついでに、pimpleにboost::shared_ptrを使うのも危険です。うっかりしたoperator=を実装すると、複数のオブジェクトで同じpimpleを共有してしまうので。

Re:pimplイディオムを語る

Posted by chanel at 2010-05-27 18:46
nice post .#ついでに、pimpleにboost::shared_ptrを使うのも危険です。うっかりしたoperator=を実装すると、複数のオブジェクトで同じpimpleを共有してしまうので。

Re:pimplイディオムを語る

Posted by valp at 2010-08-30 19:18
ふとしたきっかけで、久々にこのページに出会いました。せっかくなので、自分のコメントに補足を書き足していきます。

pimpl_ = rhs.pimpl_;について、2009-05-15 13:48のAnonymous Userさんやhiroseさんのおっしゃっているのと同じことを指摘するつもりで最初にコメントを書きました。曖昧な記述で混乱を招きましたら、申し訳ございませんでした。ただ、今になって気付いたのですが、実際にはrhsがconstのためコンパイルエラーになりますね(代入元がconstだとauto_ptrはエラーになります)。どちらにせよ、「参照カウントではなく所有権の移動という挙動を持っているためauto_ptrでは問題がある」ということに変わりはありません。

さらに、最初にコメントをしたとき私はまだ知りませんでしたが、auto_ptrを使ってはならないもう1つの理由があります。fooやbarのデストラクタ(根本的にはhoge::implのデストラクタ)が呼ばれないのです(こっちのほうがよっぽど深刻!)。pimpl_のデストラクタで、hoge::impl(不完全型)へのポインタに対しdeleteするのがその原因です。なお、boost::scoped_ptrだとこれを検出してコンパイルエラーにしてくれます。

これの対処は次のいずれかで可能です。
1. boost::shared_ptr / std::shared_ptrを使う: 動的削除子 (dynamic deleter) - 意外と知られていない??boost::shared_ptr の側面 ― Cry's Diary
2. hogeに明示的なデストラクタの定義を与える(boost::scoped_ptr / std::auto_ptrで適用可能): pimplイディオムをscoped_ptrを使って実現するときの注意 ― redboltzの日記

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_nodeconst_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

宣伝:C/C++のための開発補助ツール、GCCSenseをリリースしました

前回と同じように宣伝です。もう知っとるわいという人はそのまま閉じてください。

GCCSenseはC/C++のための開発援助ツールで、コード補完・自動構文チェックなどの機能を搭載しています。

プロジェクトのページは以下になります。ここからソフトウェアやドキュメントを入手できます。

http://cx4a.org/software/gccsense/index.ja.html

以下のページ(僕の個人ブログ)で簡単な説明とスクリーンショットおよびデモを閲覧することができます。

http://d.hatena.ne.jp/m2ym/20100415/1271306690

宣伝以上です。失礼しました。

Category(s)
c++
emacs
The URL to Trackback this entry is:
http://dev.ariel-networks.com/Members/matsuyama/gccsense-was-released/tbping

Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.