C++の class template を使えば static メンバの実体がヘッダファイルに書けるカラクリ

C++の class template を使えば static メンバの実体がヘッダファイルに書けるというテクニックがある。考えてみると不思議な動作に思える。だって分割コンパイルしたら実体が複数個出来そうじゃない?。この裏側で起こっている事を実験前に予想して試したところ、予想通りだったのでうれしかったのでメモを残す。


以下実験。

環境

g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3。Windows は試してない。

Hoge.h

class template で static メンバの実体をヘッダに書く。

#include <stdio.h>

template <typename T>
class Hoge
{
private:
    static int counter;
public:
    void incrementAndShow()
    {
        printf("counter = %d\n", ++counter);
    }
};

template <typename T> int Hoge<T>::counter = 0;

a.cpp

#include "Hoge.h"

void funcA()
{
    Hoge<bool> hoge;
    hoge.incrementAndShow();
}

b.cpp

#include "Hoge.h"

void funcB()
{
    Hoge<bool> hoge;
    hoge.incrementAndShow();
}

main.cpp

extern void funcA();
extern void funcB();

int main(int argc, char *argv[])
{
    funcA();
    funcB();
}

コンパイルと実行

a.cpp, b.cpp, main.cpp は分割してそれぞれコンパイルする。

% g++ -c a.cpp
% g++ -c b.cpp
% g++ main.cpp a.o b.o

実行すると意図通りの動作になる。でも実体が 2 つ出来てしまうんじゃないの?

% ./a.out
counter = 1
counter = 2

-S でアセンブリ見る

.LC0:
    .string "counter = %d\n"
    .section    .text._ZN4HogeIbE16incrementAndShowEv,"axG",@progbits,_ZN4HogeIbE16incrementAndShowEv,comdat
    .align 2
    .weak   _ZN4HogeIbE16incrementAndShowEv
    .type   _ZN4HogeIbE16incrementAndShowEv, @function

予想通り weak シンボルでした。b.s にも同名のシンボルがある。

objdump で見る

objdump -t ./a.out するとリンク時に counter シンボルが1つにまとまって bss に置かれている事が分かる。

0000000000400670 g     F .text  0000000000000016              _Z5funcBv
0000000000601038  w    O .bss   0000000000000004              _ZN4HogeIbE7counterE # ここ
0000000000601028 g       *ABS*  0000000000000000              _edata
0000000000400510       F *UND*  0000000000000000              __gxx_personality_v0@@CXXABI_1.3
0000000000400604 g     F .text  0000000000000020              main
00000000004004c8 g     F .init  0000000000000000              _init

結論

ヘッダだけで書けるとうれしいね><。