C 言語よりお得な C++ その1

C++を常用的に使うようになってから随分時間もたち、C++も少しだけ上達してきました。
最近では殆どのプログラムをC++で行うようになり、組み込みのような小規模なシステムでも、開発環境が提供される限りC++を使っています。

話は変わって、AVRマイコンの開発環境で「WinAVR」と言うのがあります、gcc が使えるフリーのツールキットなのですが、8ビットマイコンであるにも関わらず、C、C++でプログラミングできるフリーの開発ツールです、但し、C++のプログラミングでは、STLは使えないのですが、まぁ、数百バイトのRAMや、数キロバイトのROMでは、リソースの関係で困難と思えます、まして、データ領域とプログラム領域が分離しているアーキテクチャーでは、物理的に同じような実装をする事は難しいのかもしれません。
ただ、string、vector、map のようなコンテナは仕様に制限を設定すれば、実装する事は可能でしょう。

AVRで使える g++ は非常に良く出来ていて、最近では、AVRのプログラミングでもC++でプログラムするようになりました、何か特別な理由が無い限り、Cでプログラムを書く理由は無いと思えます。

しかしながら、典型的なCのプログラマーは、多くの勘違いをしている事が多いと思います、これはC++を本格的に学ぶ前の自分もそうでした。

(1)C++はリソースを多く消費し、プログラムサイズが大きくなる。
(2)C++は余分な処理が多く、余分なマシンサイクルを消費するので、遅い。
(3)Cのプログラミングスキルは、C++にそのまま生かせる。

まず(1)ですが、幾つかのプログラムをつくり、最適化されたアセンブラコードを観てみましたが、適正に書かれたC++コードは、Cのコードと遜色は無く、余分な命令は殆ど追加されていませんでした。
※これは処理系によっては、違う結果となる場合もありますが、WinAVR g++ ではそんな事は無く、洗練されたアセンブリコードが出るようです。

次に(2)ですが、(1)で示したように、余分な命令は殆ど無いので、余分なマシンサイクルも消費しません、場合によっては、より最適化されたコードが出る可能性も含まれている為、C言語より高速なコードが出る可能性もあります。
但し、「適正に書かれた」と言う前提が付きます、適正に書くには「学ぶ」必要がある為、C++に不慣れなプログラマーが、少しのC++のスキルを使って検証したら、別の結論に行き着く可能性があります。

そして(3)です、確かに、何かの処理をどのように細分化して、実際に動くコードを作成する能力(基本的なプログラミングのスキル)は、生かされるのですが、C++には、Cに無い様々な用法があります、それを知らないと、適切なコーディングは出来ないと思うのです、しかしながら典型的なCのプログラマーは、C++をある程度理解しているし、実際に動くコードを実装出来るので、使うつもりなら、いつでもC++でプログラミング出来る(C++は大体理解している)と思っています、しかし、C++はCに似ていますが、全く違う言語である事を認識すべきでは無いでしょうか、C++で適正なプログラミングをする為には、多くの時間を使って学ぶ必要があると思います、もしC++を理解していれば、Cより便利で安全なC++が使える環境でC++でプログラミングしない理由は無いハズなのです、C++を使わないのはそれ相応の理由が存在するのでしょう。
※幾つかの場面で、C++でプログラムしない理由は存在しますが、本当にそうなのかは検証しなければ判らないし、ケースバイケースです、Cを選択するのが最適な場合はあるででしょうが・・

これから、不定期に何回かに分けて、C++でプログラムするのに便利なテクニックや豆知識を紹介していこうと思います。
主に、昔に感じた事や重要と思われるトピックを中心に書いていこうと思います。
この文書は、正確でなかったり、実際と違う場合などがあると思います、気が付いたり指摘されれば修正していこうと思います。

良く、初心者が読むのに適したC++の参考書は?と聞かれますので、とりあえず、この本をあげておきます。
C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス

・名前空間

Cには無い便利な機能として「名前空間」があります、名前空間を活用する事で、プログラムを判りやすく、構造的にする事が出来ます。
※悪名高い Embedded C++ には名前空間がありませんが、C++を理解出来なかった人たち(日本人です)によって策定された仕様です、恥ずかしい事です。

・参照

「参照」は、C++でも最も利益のある機能の一つだと考えます、参照はポインターに似ている為、Cのプログラマーは軽視しがちですが、コンパイラーにとっては、最適化を進める上で、非常に強力な武器です、関数に渡すのがポインターでは無く、参照であれば、より進んだ最適化を行う可能性が生まれます。
また、参照では、ポインターのような、NULLチェックを行う必要は無く、構造的に参照が適用出来ない場合は、コンパイラが教えてくれます。
ポインターより少しだけ制限のある参照は、より洗練された構造をプログラムに提供し、それと同時に安全性も提供します。
参照では、const をより明確に使え、明確な意図をもって伝播させる事が出来ます、これを最初は「ウザイ」と思う人もいますが、そうでは無い事は直に理解出来ると思います。
一つの典型的な方法論として、まず参照で解決出来るか考えて、なるべく参照を使うように全体を設計し、どうしても参照に出来ない場合だけ、ポインターを使うようにします。

ポイント:
NULL について:
「NULL」はC言語のマクロであり、C++でも使えますが使うべきでは無いと考えていますし、使う理由もありません。
C++では「0」を使えば良いのです、NULLを使う理由として、数値に代入する0と、ポインターに代入する0を明確に分けたいとの理由を挙げる人もいますが、それが、わざわざ、C言語のマクロを使う理由としてメリットがあるとは考えられません、また、NULLマクロを使うには stdlib.h(cstdlib)をインクルードする必要があります。

・基本的な事

(1)標準ライブラリーのインクルード
C言語のヘッダーをインクルードする際に
C++では、C言語で使える関数も当然使えます、その際ヘッダーをインクルードしますが、C++用に専用ヘッダーが用意されています。

たとえば、「stdio.h」なら「cstdio」、「stdlib.h」なら「cstdlib」、「string.h」なら「cstring」です。
※規則は察しがつくと思います。
C++の標準的ヘッダーは、「.h」などの拡張子が無いので、それに習っているのと、C++から使う際の「おまじない」がしてあります。
※AVR の g++ では、C++ 専用のヘッダーが用意されていない為、普通のCヘッダーをインクルードします。

(2)型について
C++では、「型」を厳密に評価します。

Cの場合、たとえばポインターは典型的に以下のように書きます。

void func(char *ptr)
{
  char *tmp = NULL;

...


}

しかしC++では・・

void func(char* ptr)
{
  char* tmp = 0;

...


}

ポインターを示す「*」が、変数名に付いていたのが、型に付くようになっています。
Cでは、「ptr」の「ポインター」、「tmp」の「ポインター」だったのが、
C++では、「ptr」や「tmp」は「char」の「ポインター型」と言う考えによるものです。
しかし、多くの人が、自分流の定型記述セオリーを持っており、少しでも異なると、「気持ち悪い」と感じる為、それだけでも、テンションが下がる要因になる場合も少なくありません。
これは、慣れの問題で、もちろんコンパイラーは、「char *ptr」でも「char* ptr」でもエラー無くコンパイル出来ますが、しばらくは、自己流の狭い考えを捨てて、流れに身を任す事が寛容と考えます。

ただ、ここで問題が起こります。

  char* tmp, ptr;

このように書くと、「tmp」は「ポインター型」ですが、「ptr」は「char型」です、これは、C言語との互換を考慮して、このような不都合な事が起こります。
なので、一つの方法として、コンマで区切って、複数の変数を宣言しない事で避ける事ができます。

(3)スコープを利用した宣言

以前、Cの典型的関数では、関数内で使う変数を、頭の方で、集中的に宣言していました。

void func(void)
{
  int i, j, k;
  char c;

...


}

しかし、C++では、変数を使いたい時に、初めて変数を宣言できます。

void func()
{
  int i;
  int j;

...

  char c;
  int k;

...


}

また、スコープを使って、分離する事で、同じ変数名を何度でも宣言できます。

void func()
{
  int j;
  {
    int i;

...

  }

  {
    int i;
    int j;

...

  }
}

これは、コードがより観やすくなるだけでは無く、関数内であってもモジュール化でき、最適化に貢献できます。
ただ、↑の例で、スコープで囲まれた変数「j」を、大域の「j」と混同してしまう場合があり、注意する必要があります。※警告により回避出来ます
その都度宣言する事で、不必要なコードが生成されると思っている人がいますが、最適化されたコードは、そのような事はありません。(コンパイラーの常識、プログラマーの非常識)