C++プログラミングのメモ

Last update 16/07/25

概要

  • プログラミングをする上で,変数名の付け方とかprivate/publicの使い分けとかを勉強したことをまとめておく.

C++API

API化/OOPプログラミング

  • 目的は実装を隠すことで,利用者に影響を与えずに内部を常に改変できるようにすること.これが一番の理由.
  • それによってモジュール化が促進される.どれを外に見せるのか?を常に考えるので自然と疎結合になるようにプログラムされる,ってことかな?

C++API

カプセル化の方法

  • 宣言と定義は異なる.宣言はコンパイラに名前と型を知らせる.その実装やメモリ割り当てが定義だ.
  • 物理的な隠蔽方法として,宣言をヘッダファイルに,定義をソースファイルに書くようにする.例外はテンプレートを使う場合とインライン化の場合(後述).
  • メンバ変数はprivateにすること.publicもprotectedもだめだ.setter/getterを定義する.当然だが,setter/getterを本当に定義するべきかを考える.パフォーマンスもほぼ有意差は出ない.
  • protect指定をしてサブクラスにメンバ変数を公開すると,ユーザがそのクラスを継承したサブクラスを作ると実装を公開することになってしまう.継承を使うとオブジェクト指向設計カプセル化の利点が大幅に減少する.(継承はOOPの根本ではない,根本はカプセル化ってことなのかな?)
  • クラスは何をするか?を定義するべきであり,どうするか?を定義するのではない.これがAPI設計の基本原理だ.これに従いメンバ関数もprivateに移す.
  • でも結局privateをヘッダファイルに書いていると中身をユーザに見られてしまう.(もっと言えば#define private public)とかすると公開になってしまう.そんな時の対処法がPimplイデオム.C++プログラマなら絶対に覚えておくこと.
  • メンバ変数,メンバ関数だけじゃない,クラスだって不要なクラスはユーザに見せるべきではない.公開するクラスと公開しないクラスを意識する.
  • 約束(公開API)は最小にする.機能の追加はできても,削除は出来ないのだ.やってしまいがちなのが将来を見越した余分な抽象化の提供.実際はほとんど必要にならずに足かせになる事が多い.重複した機能を持つ別のAPIを作っちゃダメ.
  • でも,最小化すると使いにくい.便利な(最小コア関数を複合した高機能な)関数を別途用意したくなるだろう.そんな時は,それを最小コアのクラスと同じクラスにせずに別クラスにする.OpenGLGLUTが良い例.
  • APIを誤用させないようにする.例えば,Bool型の引数をenumで宣言しておく.trueとDATA_VALIDだと後者の方が意味を考えてユーザは使う.これは特定の引数をクラス化することにも応用される.とにかく,ユーザに何を書いているのか意識させることと,コンパイル時にエラーチェック出来るようにするんだ.

C++API

Cでの場合

  • namespaceが無いので名前が衝突しないようにプレフィックスを付けるか,staticでファイルローカルにする必要がある.
  • 最大のメリットは既存のC財産と共有できること.
  • オペークポインタ(オペーク:未定義の,つまり未定義の型を指すポインタ)を使って実装を隠蔽せよ.ヘッダファイルは公開されるんだぞ.
// MyClass.h
// MyClassの中身を書かない.
typedef struct MyClass *MyClassPtr;
void MyClassFunc(MyClassPtr myclass);
// MyClass.cpp
// こっちに定義を書いておけばhogeがあることなどは公開されない.
struct MyClass
{
  int hoge
  int *foo
}
  • C++から呼び出せるようにする手間は小さいから,Cを書く場合でもやっておくこと.やることは,extern "C"で囲むこと,C++コンパイラを通ること(予約後の違いなど).
  • extern "C"の意味はC++コンパイラに「これはCスタイルですよ」と教えること.具体的に何を教えているかというと,リンケージ情報を教えている.C++はオーバロードを許すから同じ名前の関数が複数存在しうる.そのため,名前が引数の型などによって符号化されている,これをマングルするという.この違いを吸収するのがextern "C" {}.
  • 具体的には,Cコンパイラでは無視して,C++コンパイラでは読むようにするために下記の方法をとる.
#ifdef __cplusplus
extern "C" {
#endif
...
...
...
#ifdef __cplusplus
}
#endif

オブジェクト指向C++

  • メリットは手続きの集合ではなく,モノとそれに対する操作の集合という形で問題をモデル化できること.これは人間が直感的に考えるやり方と相性が良い.
  • デメリットは難しいこと.is-aの関係でもないのに不要に継承したり,基底クラスのデストラクタのvirtualとか.

テンプレートベース

  • 継承が動的ポリモーフィズムなのに対して,コンパイル時のポリモーフィズムを実現できる.
  • これは速度とメモリ(プログラムサイズ)の両方に影響する.
  • 最大の問題はヘッダファイル(宣言)に定義を書かないといけないこと.これは,テンプレートはあくまでもコンパイル時の動作で,かつ,ファイル単位でコンパイルされるから型を具体的に指定してくれないと実態が作れない.ヘッダは公開されるので情報が漏れちゃう・・・