組み込みC/C++

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

case文を消したい①

case文といえば後の変更がわずらわしい悪の権化のように語られます。そんなわけでcase文の消し方を紹介しようかと思います。

 

リファクタリング対象のコードは以下のようなコードです。 

#include <iostream>
#include <conio.h>

void writeWhiteTask(void);
void writeBlueTask(void);
void writeRedTask(void);
void writeYellowTask(void);

#define  EnEightKey  0x38
#define  EnFourKey   0x34
#define  EnSixKey    0x36
#define  EnTwoKey    0x32

int _tmain(int argc, _TCHAR * argv[ ] )
{
	int iKey;

	while(1)
	{
		if(kbhit()){
			iKey = getch();
			switch(iKey){
			case EnEightKey:
				writeWhiteTask();
				break;
			case EnFourKey:
				writeBlueTask();
				break;
			case EnSixKey:
				writeRedTask();
				break;
			case EnTwoKey:
				writeYellowTask();
				break;
            default:
                break;
			}
		}
	}

	return 0;
}

void writeWhiteTask(void)
{
	std::cout << "White" << std::endl;
}

void writeBlueTask(void)
{
	std::cout << "Blue" << std::endl;
}

void writeRedTask(void)
{
	std::cout << "Red" << std::endl;
}

void writeYellowTask(void)
{
	std::cout << "Yellow" << std::endl;
}

 

ゲームも組み込みシステムも世にあるありとあらゆるプログラムは上記のような形をしていますね。これはテンキーの入力に対応してコンソールに文字列を出すプログラムになります。いい悪いは別の話しとして、このcase文を消すリファクタリングを紹介します。なおこのプログラムに対してFactoryMethodは有効ではありません。FactoryMethodでのcase文の消去法はかなり限られたパターンにしか適応できません。そちらはまたの機会に紹介しようかと思います。

・連想コンテナを使ったcase文の消去

 

#include <iostream>
#include <map>
#include <conio.h>

void writeWhiteTask(void);
void writeBlueTask(void);
void writeRedTask(void);
void writeYellowTask(void);

#define  EnEightKey  0x38
#define  EnFourKey   0x34
#define  EnSixKey    0x36
#define  EnTwoKey    0x32

int _tmain(int argc, _TCHAR * argv[ ] ) 
{ 
    typedef void (*FuncProc)(void); 
    int iKey; 

    std::map<unsigned char, FuncProc> jumpMap; 

    jumpMap.insert( std::make_pair(EnEightKey, writeWhiteTask)); 
    jumpMap.insert( std::make_pair(EnFourKey, writeBlueTask)); 
    jumpMap.insert( std::make_pair(EnSixKey, writeRedTask)); 
    jumpMap.insert( std::make_pair(EnTwoKey, writeYellowTask)); 
    
    while(1) { 
    if(kbhit()){ 
       iKey = getch(); 
       jumpMap[(unsigned char)iKey](); 
       } 
    } 
    
    return 0; 
    
}

void writeWhiteTask(void) 
{ 
std::cout << "White" << std::endl; 
} 

void writeBlueTask(void) 
{ 
std::cout << "Blue" << std::endl; 
} 

void writeRedTask(void) 
{ 
std::cout << "Red" << std::endl; 
} 

void writeYellowTask(void) 
{ 
std::cout << "Yellow" << std::endl; 
}
    

 

mapをファンクタとして使用する事で入力キーに対してcase文を使用することなく関数Jumpが実現できました。後に関数を追加する場合もmapに関数を登録するだけで変更が可能です。この方法の他にも関数Jumpテーブル(関数を要素に持つ配列)を使用する方法がありますが、入力(キーボードの入力)の値が連続的な値ではないので配列の値と入力値を対応させる為に変換テーブルが必要となり、後の変更量が多いコードになってしまいます。オブジェクティブという意味では有効ではありません。

なのですが、このコードには問題があります。

①mapが動的領域確保を行っている。

②キーから値を取り出すのに、内部的にはiteratorを回している為パフォーマンスが悪い。

①②ともPCのパッケージソフトでは許容されるものかと思いますが、特に①に関して組み込みでは厳しい壁になります。今でも組み込みシステムの多くはOSがありません。またパッケージソフトと違い、組み込みは電源が入っている限り永遠と動作してしまうプログラムです。動的なメモリ確保に対して断片化が起こりやすい上、ガベージコレクションをしてくれるOSがないためHeap領域の運用には慎重にならざるを得ません。C++の威力を発揮するためにはフレームワークSTLが不可欠ですがそれらは動的メモリを前提としている為、組み込みシステムにおいてC++導入が進まない要因となっているようです。