前の投稿で、std::map型を使って見通しのよいコーディングをしようと書いた。
####ある程度以上の組み込み環境の場合
ある程度以上の組み込み環境ならば(例:RaspberryPiやZynqなどのボード)g++を使え、STLを使うことに何の問題もないはずだ。そしてSTLのコンテナを使ってプログラムを記述できる。PCで開発していて、まずは正しく動く版を作る分には、STLを積極的に使うべきだと私は考える(一部の商用コンパイラのC++の実装に不安があって、そのコンパイラを使うSTLの実装に不安があった昔とは状況が異なると思う。当時は、STLを利用して書かれていたコードを非STLコードに書き換えることを求められたという話を聞いたことがある。)。STLは外部仕様が明解であり、複数のメンバーで理解が共通化しやすい。STLを使って楽をした分だけ他の部分のアルゴリズムを十分に仕様を明確にしてテストをすることに労力をさける。
最終実装がPCである場合には、STLのままにできるであろうし、ある程度以上の組み込み環境でもSTLのままにするという選択があるだろう。
この記事では、それでもSTLを最終実装に使えないという人のために、C++のソースを自動生成させることができることを示すことで、PC版で正しく動くコードを開発する時点ではSTLを使うことを提案するものです。
####std::map型が使えない環境への移行を楽にするスクリプト
次のPythonスクリプトは、withMap.cppファイル中のstd::mapのマッッピングのデータを読み込んで、STLを使わないif() else if()文の並びを生成させるものです。
# -*- coding: utf-8 -*-
def parseMap(name):
u"""
std::map<std::string, int>atmomicNumbers を読み取る。
mapのキーと値とを読み取り、Pythonの辞書に代入する。
"""
d = {}
for line in open(name, "rt"):
oline = line.strip().replace(",", " ")
if len(oline) > 1 and oline[-1] == ",":
oline = oline[:-1]
f = oline.split()
if len(f) == 4 and f[0] == "{":
d[int(f[2])] = f[1]
return d
if __name__ == "__main__":
u"""
読み取った辞書を元にC/C++言語のif else if 文を生成する。
"""
d = parseMap(name="withMap.cpp")
keys = d.keys()
keys.sort()
k = keys[0]
print """if(strcmp(instr, %s) == 0){
r = %s;
}""" % (d[k], k),
for i in range(1, len(keys)):
k = keys[i]
print """else if(strcmp(instr, %s) == 0){
r = %s;
}""" % (d[k], k),
入力データのstd::map型のデータを含むC++ファイル
#include <iostream>
#include <string>
#include <map>
/** 元素記号から原子番号を対応させるmap型のコンテナ
* 宣言と同時に初期化を実施。
* (第2周期のNeまでしか記載していない。)
*/
std::map<std::string, int> atmomicNumbers = {
{ "H", 1 },
{ "He", 2 },
{ "Li", 3 },
{ "Be", 4 },
{ "B", 5 },
{ "C", 6 },
{ "N", 7 },
{ "O", 8 },
{ "F", 9 },
{ "Ne", 10 }
};
int main(int argc, char* argv[]){
std::string symbol = "Be";
std::cout << symbol << " " << atmomicNumbers[symbol] << std::endl;
}
実行結果
if(strcmp(instr, "H") == 0){
r = 1;
} else if(strcmp(instr, "He") == 0){
r = 2;
} else if(strcmp(instr, "Li") == 0){
r = 3;
} else if(strcmp(instr, "Be") == 0){
r = 4;
} else if(strcmp(instr, "B") == 0){
r = 5;
} else if(strcmp(instr, "C") == 0){
r = 6;
} else if(strcmp(instr, "N") == 0){
r = 7;
} else if(strcmp(instr, "O") == 0){
r = 8;
} else if(strcmp(instr, "F") == 0){
r = 9;
} else if(strcmp(instr, "Ne") == 0){
r = 10;
}
このように、規則正しいデータの部分から、プログラムの一部を自動生成させることは容易なことです。ですから、見通しのよいデータ形式を用いてプログラムの健全性を確保することを主張します。
std::mapを使ってプログラムを記述すれば、データが増えることの影響は
{ "Ne", 10 }
以降の行を足すだけになって、それ以外の部分のコードの変更を不要とします。
まず正しく動くこと、正しく動いていることを担保できる仕組みを確保すること、次に実装上の性能(処理速度、プログラムサイズ)を確保することだと考えます。
####補足:
上記の例では、std::mapを使わずに、
char* [] atomicSymbols = {
"H", "He",
"Li", "Be", "B", "c", "N", "O", "F", "Ne"
}
という配列を使って処理することも可能である。しかし、ここでは、キーと値とを対応付けるロジックに標準のライブラリを使うことを提案するためにstd::map<std::string, int>
型を用いている。
####付記:
スクリプト言語を使ってプログラムの一部を生成することは、一部の人には昔から使われている。データだけ入れ替えた同じような内容を山ほど記述するときに、データを元にそのプログラムの一部を自動生成してきた。データはしばし頻繁に更新されるので、一度手作業で書いてそれっきりというわけにはいかないこともある。そのようなアプローチは、どのような言語に対しても用いることができる。
たとえば、RatforというFORTRANの拡張言語は、プリプロセッサを用いることで、FORTRAN言語の言語仕様上の欠点を隠すものであった。行っている処理の見通しをよくすることは、プログラムの健全性を早い段階で担保することだと私は考える。
追記:
『プログラミング作法』 第9章 記法「9.5 プログラムを記述するプログラム」には、名前とコメントを含んだヘッダファイルに基づいてエラーメッセージを生成するようになっている。その本では、perlのスクリプトで自動変換している。このようにスクリプト言語を使ってC/C++言語での開発を加速することができる。
enum {
Eperm, /* Permission denied */
Eio, /* I/O error */
Efile, /* File does not exist */
Emem, /* Memory limit reached */
Espace, /* Out of file space */
Egreg /* It's all Greg's fault */
};
から次の宣言を生成している例が書かれている。
char *errs[] = {
"Permission denied", /* Eperm */
"I/O error", /* Eio */
"File does not exist", /* Efile */
"Memory limit reached", /* Emem */
"Out of file space", /* Espace */
"It's all Greg's fault", /* Egreg */
};