組み込みC/C++

C/C++リテラシー向上のためのページ

クラスTemplate内のメンバの実体化

クラスTemplate内のメソッドはヘッダ内に定義する事となっています。試しに.cpp側で定義をするとコンパイルは通るのですがリンクエラーになってしまいます。そういえばコンパイルというのはcppファイル毎に行うものでした。Templateクラスのcppファイルをコンパイルする時には型情報が決まっておらず結局実体は展開されません。そのくせヘッダからはあたかも実体がいるように見えてしまうのでリンクエラーとなります。(この状況を回避するのにプロトタイプと同じように明示的型情報を宣言しておけば可能ではあります)そのためクラスTemplateのメソッドの定義はヘッダで行います。

すこし話しを変えて、静的変数というのはヘッダで定義するとオブジェクト毎(cppファイル毎)に実体を作ってしまうものでした。資源の無駄遣いな上変数が共有されないのでバグの温床となります。これを避ける為、静的メンバ変数ではクラス内で宣言するだけでなく実体をcpp側に定義する必要があります。このおかげでいくつインスタンスを作ろうとユニークな実体が保証されます。

ここでふと疑問に思いました、クラスTemplateでは静的と言えど実体をcpp側に定義しません。ということはコンパイル単位ごとに実体が作られてしまうのではないか?それはメソッドについても同じことが言えるのではないか?という事です。そこで下記のようなプログラムで調べてみました。

 

main.cpp 

#include "SampleTemplate.h"
#include"sub.h"

int _tmain(int argc, _TCHAR* argv[])
{
    SampleTemplate<char>::func_a("main");

	int maincode[2] = {'niam','\0'};

	SampleTemplate<int>::func_a(maincode);

	SampleTemplate<char>::func_b("main");

	sub();

	return 0;
}

 

sub.h 

void sub(void);

 

sub.cpp 

#include "SampleTemplate.h"
#include <string>
using namespace std;

void sub(void){

    SampleTemplate<char>::func_a("sub");

	int subcode[2] = {'bus','\0'};

	SampleTemplate<int>::func_a(subcode);

	SampleTemplate<char>::func_b("sub");

}
    

 

SampleTemplate.h 

#pragma once
#include <iostream>
using namespace std;

template <class T>
class SampleTemplate
{
public:
	static T func_a(T* tMa1){
		
		T (*func)(T* tMb) = SampleTemplate<T>::func_a;
	
		cout << "func_a " << (char*)tMa1 << "=" << hex << func << endl;
	
		return *tMa1;
	}
	
	static void func_b(char *psC){
	
		void (*func)(char* psD) = SampleTemplate<char>::func_b;
		
		cout << "func_b "<< (char*)psC << "=" << hex << func << endl;
	
	}

};    

 

この例ではmain.cppとsub.cppがそれぞれでコンパイルされます。SampleTemplate.hで定義されたメソッドが、もしコンパイル単位毎に別々の実体になるとしたらmainからの呼び出しとsubからの呼び出しとでは、メソッドのアドレスが異なるアドレスになるはずです。なおint型で文字列を渡すために無茶な初期化をしていますが、今回は実体の定義が複数個作られるかのチェックを目的にしていますので言及は控えます。

 

このプログラムの結果は以下になります。

 

func_a main=00051109
func_a main=0005114A
func_b main=00051195
func_a sub=00051109
func_a sub=0005114A
func_b sub=00051195

 

func_aの実体には全部で二つのアドレスが割り振られていますが、これはchar型とint型でそれぞれ展開されたものであり、mainから呼んだfunc_aもsubから呼んだfunc_aも同じアドレスを指しています。コンパイル単位ごとに実体は作られていない事が判りました。ただしTemplateインスタンス(型情報)が違う場合は複数個展開されています。おそらくですがコンパイル単位(オブジェクトファイル)ではそれぞれにTemplateクラスのメンバの実体化をしているものと思います。それらをリンカする際に名前の重複を発見してそれがTemplateと判断した場合にはリンク順に実体を逐次消していくものと思われます。ただの静的変数の場合ラべリングされないのでこういった処理はできませんでしたがクラスTemplateのメンバはラべリングされるのでこういったリンカの処理が可能なのだと思います。

 

まとめ

クラスTemplateのメンバの実体はコンパイル単位ごとに複数個作られない。

クラスTemplateのメンバの実体はTemplateインスタンス(型情報)ごとに複数個作る。