Heaven's Kitchen

○ [C++]関数の戻り値のコピー

CやC++の関数の戻り値は値渡しならぬ値返し(この言い方が一般的かどうかは寡聞にして知りませんが)なので,関数の呼び出し側に,戻り値の値がコピーされます。

#include <iostream>
using namespace std;

struct Foo { 
    int x, y;
};

Foo *fp = 0;

Foo foo() 
{ 
    Foo f; 
    f.x = 10; f.x = 20; 
    fp = &f;
    return f; 
}

int main()
{
    Foo f;
    f = foo();
    if (fp == &f) {
	cout << "same" << endl;
    } else {
	cout << "different" << endl;
    }
    return 0;
}

こんな風に書くとfoo関数の中で作られたfは,main関数のfにコピーされます。(正確にはFooのoperator=が呼び出されます。デフォルトではすべてのメンバをコピーするだけです。)mainのfはコピーなので当然foo()の中のfとは何も関係がありません(値は同じだけど)。全く別のオブジェクトなのでfoo()のfとmain()のfはアドレスを比較しても違うアドレスがとれます。なので上のmainを実行すると"different"と表示されます。

この例のように戻り値の型(Foo)が小さい場合はコピーも手間にはなりませんが,中に大きな配列を抱えているようなオブジェクトのコピーは深刻なオーバーヘッドになり得ます。

オブジェクトへのポインタを返すようにすれば,コピーの手間はなくなりますが,そのオブジェクトの所有権が曖昧になります。Factoryのように,それとわかるパターンの中で使うなら話は別ですが。

Cではこれをさける手段はない(と思う)のですがC++だとコピーが発生しない場合があるみたいです。仕様かどうかはわかりません(笑 というかほぼ間違いなく実装依存だと思われます。

foo()の戻り値をFooのコンストラクタで受けるとある条件のもとではコピーが発生しなくなります。具体的には上の例のmainを次のように書き換えます。

int main()
{
    Foo f = foo();
    if (fp == &f) {
	cout << "same" << endl;
    } else {
	cout << "different" << endl;
    }
    return 0;
}

ただし,ある条件というのがポイントです。まずfoo()が返すFoo型の値がfooの中で作られる自動変数である必要があります。(たぶん)あと,クラスFooの定義によっても結果が変わります。

Fedora Core 6のg++(version 4.1.2)ではsizeof(Foo)の値が8バイトよりおおきくなると上の結果が"same"になります。PowerPC Mac OS X Tigerのg++(version 4.0.1)ではFooの大きさには関係なくFooに明示的なコピーコンストラクタを定義してやれば"same"が出力されるようになります。ただし,条件は他にもあるかもしれません。

実験用のコード。

#include <iostream>
using namespace std;

struct Foo {
    int x, y;
};

struct Bar {
    int x, y, z;
};

struct Baz {
    int x, y;
    Baz() {}
    Baz(const Baz&) {}
};

Foo *fp = 0;
Bar *bp = 0;
Baz *zp = 0;

Foo foo()
{
    Foo f;
    fp = &f;
    return f;
}

Bar bar()
{
    Bar b;
    bp = &b;
    return b;
}

Baz baz()
{
    Baz z;
    zp = &z;
    return z;
}

int main()
{
    Foo f = foo();
    Bar b = bar();
    Baz z = baz();

    if (&f == fp) {
	cout << "foo OK" << endl;
    }
    if (&b == bp) {
	cout << "bar OK" << endl;
    }
    if (&z == zp) {
	cout << "baz OK" << endl;
    }

    return 0;
}

手元のFedoraでは

bar OK
baz OK

手元のibookでは

baz OK

と表示されます。

と,いうわけで複雑なオブジェクトを返す関数も,コンストラクタで受ければコピーは発生しないことが期待できるってことで。てかまあ当然考えられる最適化ですねってどこかに書いてあった気がする。

同じgccでも環境によって結果が異なるので,このことを前提にしたコードは書けませんが,効率が悪いからといって,でかいオブジェクトをreturnするのを避ける必要は,あんまりなさそう。ほんまかいの。

○ スマスマ

宮崎あおいがやばい!

○ PerlでHello World

そろそろ中間発表の準備で参ってきたので,現実逃避気味に日記でも書こうかと思いたちました。

こないだ研究室の開発合宿なるものに行ってきたのですが,そこで先輩に教えてもらったPerlのコード

''=~('"`_[[).[|`%,,/`[/[@$~)^##'^'
_$+)@@/^(@@@@@,@),@_#|^
')

これでHello Worldが出ます。これの仕組みは以下のような感じのようです。

eval('[[).[|`%,,/`[/[@$~)^'^'+)@@/^(@@@@@,@),@_#|');

例えばCで

main()
{
    int i;
    char *a = "\"`_[[).[|`%,,/`[/[@$~)^##";
    char *b = "\n_$+)@@/^(@@@@@,@),@_#|^\n";
    for (i=0;i<25;++i)
        putchar(a[i] ^ b[i]);
}

とかやってみると驚きの結果が。

べつにXORでなくてもいいので|や&で同じような

暗号がたくさんつくれるー,と思ったけど実装したら

後悔しそうなのでやめました。

○ フーコ

森博嗣のスカイクロラシリーズ読破しました。

フラッタリン〜クレイドゥの流れがすごい好きです。クリタ(たぶんクリタだよねぇ…)がフーコと逃げるとこがとっても切ない。

映画化楽しみだ。

という現実逃避。

Valid XHTML 1.0! Valid CSS!