組み込みC/C++

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

多相性について

CからC++へプログラムを移行する動機として多相性を使いたいというのがあるかと思います。さて、次のようなCソースがありました。これを多相性を使って、オブジェクティブなソースにしてみましょう。

 

void *copy_char(char *to, const char *from, int length ){

    int i;
	for(i = 0; i<length; i++){
		to[ i ] = from[ i ];
	}

	return to;
}

void *copy_short(short *to, const short *from, int length ){

	int i;
	for(i = 0; i<length; i++){
		to[ i ] = from[ i ];
	}

	return to;
}

int main(){

	const char base1[ ]="abcde";
	char buff1[10];

	const short base2[ ] = { 12568, 25462, 2565 };
	short buff2[10];

	copy_char( buff1, base1, sizeof(base1));
	copy_short( buff2, base2, sizeof(base2)/sizeof(short));

	return 0;
}

 

上のソースはやっている事は全く一緒にも関わらず、型が違うために違う関数を用意してしまったコードです。さてC++を学び始めた人は次のようなコードにするかもしれません。

 

void *copy(char *to, const char *from, int length ){

    int i;
	for(i = 0; i<length; i++){
		to[ i ] = from[ i ];
	}

	return to;
}

void *copy(short *to, const short *from, int length ){

	int i;
	for(i = 0; i<length; i++){
		to[ i ] = from[ i ];
	}

	return to;
}

int main(){

	const char base1[ ]="abcde";
	char buff1[10];

	const short base2[ ] = { 12568, 25462, 2565 };
	short buff2[10];

	copy( buff1, base1, sizeof(base1));
	copy( buff2, base2, sizeof(base2)/sizeof(short));

	return 0;
}

 

こんな感じのソースC++の本の最初の方にオーバーロードのサンプルとして載ってますよね。でも一般にオーバーロードは引数の数が違う場合に使う事が多いようです。引数の数が変わらない場合は曖昧さがあって予期しない関数が呼ばれてしまう可能性があるからです。しかも呼び出しは共通化出来ましたが、結局関数が二つ用意されているので、旨みがほとんどないです。

他の方法を考えてみましょう。型違いと聴くとジェネリックプログラミングってなりますよね。そうなると次のようなコードになるかもしれません。

 

template <class T>
void *copy(T *to, const T *from, int length ){

    int i;
	for(i = 0; i<length; i++){
		to[i] = from[i];
	}

	return to;
}

int main(){

	const char base1[ ]="abcde";
	char buff1[10];

	const short base2[ ] = { 12568, 25462, 2565 };
	short buff2[10];

	copy<char>( buff1, base1, sizeof(base1));
	copy<short>( buff2, base2, sizeof(base2)/sizeof(short));

	return 0;
}

関数テンプレートを使うと、先ほどのコードよりもコーダーの負担が減っていて良いのですが、ROMには結局展開されています。旨みはありますがROM的にはいまいちです。さて、C上級者は次のようなプログラムを書くかもしれません。

 

#define COPY(X) typedef const X const##X; \
void *copy_ ##X(##X *to, const ##X *from, int length ) \
    {\
	int i;\
	for(i = 0; i<length; i++){\
		to[ i ] = from[ i ];\
	}\
	return to;\
}

COPY(char)
COPY(short)

int main(){

	const char base1[ ]="abcde";
	char buff1[10];

	const short base2[ ] = { 12568, 25462, 2565 };
	short buff2[10];

	copy_char( buff1, base1, sizeof(base1));
	copy_short( buff2, base2, sizeof(base2)/sizeof(short));

	return 0;
}

これはトークン連結演算子を使ったジェネリックプログラミングのC版です(C++でもできますが)。テンプレートとの違いはプリプロセッサで展開されるか、コンパイラで展開されるかの違いで結局ROMは食います。やたら難解なのでまったく勧めません。さておそらく一番いい変更は次のようなプログラムかと思います。

 

void *copy(void *to, const void *from, int length ) 
{
    int i;
	char *to_buff = (char *)to;
	char *from_buff = (char *)from;

	for(i = 0; i<length; i++){
		to_buff[ i ] = from_buff[ i ];
	}
	return to;
}

int main(){

	const char base1[ ]="abcde";
	char buff1[10];

	const short base2[ ] = { 12568, 25462, 2565 };
	short buff2[10];

	copy( buff1, base1, sizeof(base1));
	copy( buff2, base2, sizeof(base2));

	return 0;
}

汎用ポインタ void * を使えば、ポインタであれば型情報を無視して受け取る事ができます。とても基本的ですし単なるmemcpyなのですが多相性を考える時には大事なポイントです。

※sizeof()の使い方には注意が必要です。