読者です 読者をやめる 読者になる 読者になる

使いもしないのにC++のtemplateを毛嫌いする全ての人に

c/c++

C++AdventCalendarの記事です。

さて、
生配列使ってますか?
tr1::array(boost::array)
使ってますか?

生配列使っていると答えた貴方、
→まず死ね。

はい、arrayが常識ですよね。
さて、とはいえ、
「テンプレートを使うと遅いしコードがでかいし」
「生配列が一番速いしコードが小さいし」
「なのでテンプレート禁止」
なんて話を聞くこともあるかと思いますが、
こういう事をいう人は大抵「テンプレートを書いたことがない」のに言ってます。


なぜか?
こういう人が本当に心配しているのは「テンプレートが肥大化すること」じゃないのです。
「テンプレートが書けないし読めないのを認めたくないからです」
多くはCの老害だからですが、そういう人は放っておいてC++な人はきちんとテンプレートを使いましょう。

だって多くのテンプレートのコードは大きくもなければ非効率でもないからです。
その上、デバッグ用のチェックコードも仕込まれてますし、
ちょっとしたことから大きなことまで様々なところで便利です。


とはいえ実際にアセンブリを見たことがありますか?

じゃ、やってみましょう。
gcc を使って-O5でのコードを比較します。
(Mac上でg++-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)を使用しています)
gccでは-Sをつけることでアセンブリコードを吐いてくれるので非常に比較が楽です。
g++ -Wall -S -O5 main.cpp
でやってみました。

enum {
     DATA_SIZE = 10000000,
};

int data[DATA_SIZE];

int hoge() {
    for (int i = 0; i < DATA_SIZE; ++i) {
        data[i] = rand();
    }
    return data[0];
}

と、

typedef tr1::array<int, DATA_SIZE> FooArray;
typedef FooArray::iterator FooArrayIt;
FooArray data2;

int hoge()
{
    for (FooArrayIt it = data2.begin(); it != data2.end(); ++it) {
        *it = rand();
    }
    return data2[0];
}

で比べてみましょう。
生配列は、

LFB1530:
     pushq     %rbp
LCFI6:
     movq     %rsp, %rbp
LCFI7:
     pushq     %r12
LCFI8:
     pushq     %rbx
LCFI9:
     leaq     _data(%rip), %rbx
     leaq     40000000(%rbx), %r12
     .align 4,0x90
L11:
     call     _rand
     movl     %eax, (%rbx)
     addq     $4, %rbx
     cmpq     %r12, %rbx
     jne     L11
     movl     _data(%rip), %eax
     popq     %rbx
     popq     %r12
     leave
     ret

テンプレートでは、

LFB1531:
     pushq     %rbp
LCFI2:
     movq     %rsp, %rbp
LCFI3:
     pushq     %r12
LCFI4:
     pushq     %rbx
LCFI5:
     leaq     _data2(%rip), %rbx
     leaq     40000000(%rbx), %r12
     .align 4,0x90
L4:
     call     _rand
     movl     %eax, (%rbx)
     addq     $4, %rbx
     cmpq     %r12, %rbx
     jne     L4
     movl     _data2(%rip), %eax
     popq     %rbx
     popq     %r12
     leave
     ret

完全に一致!
と、最適化をかけるとちゃんと一致します。(かけないと一致しません)
多くのコンパイラではテンプレートコードの最適化はかなり賢く完全に一致します。

ではタプルでは?

typedef tr1::tuple<int, int, int> TripleInt;

TripleInt moo()
{
     TripleInt t;
     tr1::get<0>(t) = 1;
     tr1::get<1>(t) = 2;
     tr1::get<2>(t) = 3;
     return t;

}

typedef struct StructTripleInt
{
     int one;
     int two;
     int three;
};

StructTripleInt noo()
{
     StructTripleInt t;
     t.one = 1;
     t.two = 2;
     t.three = 3;
     return t;
}

これも、

LFB3381:
     pushq     %rbp
LCFI2:
     movq     %rsp, %rbp
LCFI3:
     movl     $1, (%rdi)
     movl     $2, 4(%rdi)
     movl     $3, 8(%rdi)
     movq     %rdi, %rax
     leave
     ret

と、

LFB3382:
     pushq     %rbp
LCFI4:
     movq     %rsp, %rbp
LCFI5:
     movl     $2, -12(%rbp)
     movl     $1, -16(%rbp)
     movq     -16(%rbp), %rax
     movl     $3, %edx
     leave
     ret

になり、殆ど変わらないコードになります。
コンパイラにもよりますが、最近のコンパイラは頭良いのでこの辺りは心配ないと思います。


こういった「最適化されたときにゼロコストになるテンプレート」は思いの外沢山あります。
組み込みなどで.textの肥大が辛い場合「テンプレートを使わない」という選択肢もなくはないですが、
まず「テンプレートを使わない」と決める前にきちんと自分の使っているコンパイラの吐くコードを確かめましょう。
懸念としてコンパイル速度が若干遅くなる懸念はありますが、開発の効率性や保守性を考えた時、
テンプレートを使わない、という選択肢はないと思います。

また空間や速度の効率性に関してはEfficient C++等も参考になります。

Efficient C++パフォーマンスプログラミングテクニック

Efficient C++パフォーマンスプログラミングテクニック

  • 作者: ダブブルカ,デビットメイヒュ,浜田光之,Dov Bulka,David Mayhew,浜田真理
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/07
  • メディア: 単行本
  • 購入: 9人 クリック: 105回
  • この商品を含むブログ (31件) を見る