ラベル C の投稿を表示しています。 すべての投稿を表示
ラベル C の投稿を表示しています。 すべての投稿を表示

2017年1月10日火曜日

Cのselect、epollを使ったI/O多重化


複数のファイルディスクリプタを監視し、その中のいずれが入出力可能な状態であるかを確認するCのシステムコールとしては、select、epoll、kqueue(BSD系)が有名だろう。
ここでは、selectとepollを使い、1プロセス1スレッドでのイベント駆動によるI/O多重化コードを備忘録として残しておく。

(参考)
最近ではselectではなく、epoll、kqueueが使われることが多い。
selectは待ち受けられるファイルディスクリプタの数に上限があり、またパフォーマンス問題も存在するためである。
I/Oからの入力に応じて発生するイベントを処理するライブラリとしてはlibevent, libev, libuvなどがあるがこの内部でもepollやkqueueが利用されている。
※libevはlibeventの速度改善、FDの制限撤廃の対応がされた改良版である。
※libuvはnode.js用のためにlibevをベースに開発されたライブラリである。



(select.c)


(epoll.c)


$ gcc ファイル名

$ ./a.out /dev/tty /dev/input/mouse0 10
hello
/dev/tty: hello

no input after 10 seconds

マウス操作
/dev/input/mouse0: (


(補足)
readで指定したバッファサイズ以上のデータがあった場合、ループでepoll_waitの処理へ戻るとはどのような動作をするか分かるだろうか。
残りのデータを引き続き読み込めると判断されるた、待ち状態にはならない。
しかし、次のようにイベント条件を指定した場合は一度しかイベントは発火しない。
ev.events = EPOLLIN | EPOLLONESHOT

イベントを継続させる場合には、再度epoll_ctlを同じepfdとEPOLL_CTL_MOD値を渡して呼べばよい。
epoll_ctl(epfd, EPOLL_CTL_MOD, fd1, &ev)

2015年5月18日月曜日

linuxで稼働する操作コマンド記録ツール


linux上で操作されたオペレーションをログとして日時と共に記録するツール(logrec)を作成した。
telnet、ssh等で別サーバへログインしていっても、そこでの操作記録はすべてログイン元サーバで一元的に保存される。
対象は入力デバイスからの操作のみであり、コマンドの結果は記録しないものとする。


(参考)
Cによるselectを利用した簡易プログラム
コマンドライン引数を取得するCのサンプルコード
【review】 Unix/Linux プログラミング 理論と実践



◆ コード
logrec.cの一つだけである。
全コードはgithubを参照のこと。
https://github.com/alpha-netzilla/logrec
※linuxで使われるscriptというコマンドのコードを改造している。


◆ 使い方
1. ビルド
$ gcc -lutil logrec.c -o logrec

2. 実行
引数は結果を保存するファイルである。
$ ./logrec /var/logrec/some_user

3. 記録
コマンドを適当に打ち操作を記録させる。
当然コマンドは何でもよい。
$ ls
hoge piyo
$ ssh example.com
example.com's password:***
Last login:...
$ ls
foo bar

4. 確認
ツールが起動した旨と、実行したコマンドのみが日時とともに記録されていることを確認する。
$ cat /var/logrec/some_user
yyyy-mm-dd 00:00:00 logrec started
yyyy-mm-dd 00:00:05 $ ls
yyyy-mm-dd 00:00:10 $ ssh example.com
yyyy-mm-dd 00:00:15 $ ls

ファイルへの結果は即フラッシュさせないで、バッファリングして出力させている。
そのため、数コマンドでは結果を確認できないかもしれない。
何文字以上で書き込むかはOSに任せている。

5. 停止
Ctrl + D



◆ 動作ロジック
コード内に適宜コメントを残しているため、ポイントだけを記載しておく。

(ポイント1)
目的の実現のために、仮想端末(pty)を利用している。
仮想端末は、物理的な端末の入出力を任意のプログラムでエミュレーションする仕組みである。

ツール内で親がforkしその後にその子がさらにforkして子プロセスを生む。
それぞれの役割を記す。

・親:parent
端末から入力を読み込んで、pty(master)へ書き出すプロセスである。

・子:child1
pty(master)から入力を読み込み、端末に書き出すプロセスである。

・子:child2(child1から呼ばれる)
shellを起動し、制御端末になるプロセスである。
ただし、このプロセスをセッションリーダにするため親子関係は断ち切らせる。
pty(slave)からの入出力用ファイルディスクリプタをこのプロセスが受け持つ。


inとoutの流れを見る際にptyは、masterとslaveが2つではじめて仮想端末の役割を果たすことを注意しておきたい。
例えば、pty(master)に入った通信は必ずpty(slave)へ出力され、
pty(master)からの出力はpty(slave)からの入力が対象となる。


概略図を書いてみる。

+--------+
| client |
+--------+
  |   ^
  |   |
  |   +-----------------+
  v                     |
+--------+   fork   +--------+    fork     +---------+
| parent | -------> | child1 | ----------> |  child2 |
+--------+          +--------+             | ->shell |
    |                    ^                 +---------+
    |                    |                      ^
    |                    |                      |
    |                    |                      |
    |                    |                      |
    |             + -----|----------------------v---------+
    |             | +-------------+        +------------+ |
    +-------------> | pty(master) | <----> | pty(slave) | |
                  | +-------------+        +------------+ |
                  +---------------------------------------+


sshでも同様の仕組みが使われている。
なじみが深いリモートログインの仕組みで役割を読み替えると分かりやすいだろう。

・client: sshクライアント
parent: sshd
child1: なし(forkは一回だけである)
child2: shell

logrec内でchild1プロセスをforkした理由は、
ここで操作ログを取得するロジックをはさみたかったためである。


(ポイント2)
実行されたコマンド単位に、その先頭に日時を付与したい。
そこで改行時を終了時として、コマンドの終わりと判断する。

parentプロセスからの入力をchild1に記録させるが、
両プロセス間で日時の付与するタイミングをどのように知らせて、連携をとればよいだろうか。

いくつか手段はあるが、今回は共有メモリを利用することとした。
プロセス同士はメモリ空間を共有しないのだが、共有するための専用の空間がある。
改行がくれば、そのことを知らせるフラグを立て、日時を追加する。



(参考)
http://rachid.koucha.free.fr/tech_corner/pty_pdip.html
http://www.glamenv-septzen.net/view/563
http://www.glamenv-septzen.net/view/854
http://alpha-netzilla.blogspot.com/2014/12/expect.html



2015年4月30日木曜日

コマンドライン引数を取得するCのサンプルコード


自分用メモ。

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

int main(int argc, char** argv) {

  int aopt = 0;
  int bopt = 0;
  int copt = 0;
  char *bparam = NULL;
  char *cparam = NULL;

  struct option longopts[] = {
      {"aaa", no_argument,       NULL, 'a'},
      {"bbb", required_argument, NULL, 'b'},
      {"ccc", optional_argument, NULL, 'c'},
      {0, 0, 0, 0}
  };

  int opt = 0;
  int longindex = 0;
  while ((opt = getopt_long(argc, argv, "ab:c::", longopts, &longindex)) != -1) {
    printf("index: %d, option: %s\n", longindex, longopts[longindex].name);
    switch (opt) {
    case 'a':
      aopt = 1;
      break;
    case 'b':
      bopt = 1;
      bparam = optarg;
      break;
    case 'c':
      copt = 1;
      cparam = optarg;
      break;
    default:
      printf("error %c \n", opt);
      exit(1);
    }
  }

  printf("--aaa(-a) = %d\n", aopt);
  printf("--bbb(-b) = %d: %s\n", bopt, bparam);
  printf("--ccc(-c) = %d: %s\n", copt, cparam);

  int i;
  for(i = optind; i < argc; i++) {
    printf("arg: %s \n", argv[i]);
  }
}


$ ./a.out --aaa --bbb="hoge" --ccc="piyo"
index: 0, option: aaa
index: 1, option: bbb
index: 2, option: ccc
--aaa(-a) = 1
--bbb(-b) = 1: hoge
--ccc(-c) = 1: piyo


--をつけたロングオプションの場合は、=を省略できるが、
--cccは引数をオプション扱いにしているので
最後のpiyoはロングオプションの引数扱いにはならない点が注意が必要である。
$ ./a.out --bbb "hoge" --ccc "piyo"
index: 1, option: bbb
index: 2, option: ccc
--aaa(-a) = 0
--bbb(-b) = 1: hoge
--ccc(-c) = 1: (null)
arg: piyo




2014年7月17日木曜日

コンパイルや、ビルド時に付与したいオプション


コンパイルや、ビルド時にセキュアなオブジェクトコードを生成するために
利用すべきオプションを記録しておく。

g++ ビルド時のオプション
それぞれのオプションの意味はmanを参照してほしい。

$ g++ \
-g \
-fmudflap \
-lmudflap \
-fstack-protector \
-ftrapv \
-O2 \
-fno-strict-aliasing \
-Wall \
-Wextra \
-Wformat=2 \
-Wstrict-aliasing=2 \
-Wcast-qual \
-Wcast-align \
-Wwrite-strings \
-Wconversion \
-Wfloat-equal \
-Wpointer-arith \
-Wswitch-enum \
-Woverloaded-virtual \
-Weffc++ \
-o hoge.o \
hoge.cc


さらに、メモリの取得と解放のチェックを実施する環境変数を指定する。
$ export MALLOC_CHECK_=1

fmudflapオプションを付与して実行ファイルを作った場合、
mudflapライブラリを指定して実行する必要がある。
$ ./hoge.o -lmudflap

※gccとの差異
下はg++のみで有効。gccでは使えない。
-Woverloaded-virtual
-Weffc++



デバッグ時のみに使うべきオプション
デバッグ用のオプションなので、リリースビルド時には外すこと。

● -g
いわずもがな、デバッグオプション。
コンパイル、リンク時にDEBUG情報を付加して、
gdbなどのデバッガを使用するときに必要となる。


● -fmudflap
-gオプションと一緒に使う
また、リンク時にライブラリの指定が必要である。
-lmudflap

mudflapはコンパイル時に不正なポインタアクセスを検出できる。
仕組みとしてはアドレス周辺にチェックコードを埋め込むことで、
heap、stack、data、bss領域の変数の検査を行う。

mudflapが使えない場合はパッケージのインストールが必要である。
$ sudo yum install libmudflap libmudflap-devl

ポインタ周りのチェックにvalgrindを使うことも多いだろう。
mudflapとvalgrindの大きな違いは、
valgrindは解析対象の再コンパイルとリンクが不要で使える、
しかし、stack、data、bss変数の検査は不可(heapは可)、
また解析速度がmudflapに比べて遅い、
あたりだろう。
ついでなので、valgrindの使い方も後半に少しだけまとめておく。

同様の機能として-D_FORTIFY_SOURCEオプションを使ってもよい。
mudflapはデバッグ用の機能であったが、これはランタイムのオーバーヘッドが少ないため、
リソースビルドにも利用できる。
$ g++ -O2 --D_FORTIFY_SOURCE=1 ~
-O1以上の最適化が必須である。
コンパイル時にも、実行時にもオーバーフローチェックが走る。


● -fstack-protector
スタック上に確保されたchar変数あふれによって生じる、
リターンアドレスの書き変えを防止、検出する。

関数ポインタの指し先を上書けないように、
スタックレイアウトの調整、またcnary(カナリア)というガード値を
別途スタック上に挿入しそこへの改ざんの有無をチェックすることで実現する。

スタック破壊検出コードの生成はすべての関数に対しては行われないことに注意が必要である。
8バイト以上のchar配列が確保される関数だけが保護対象となる。
なお、この8バイトという閾値は、次のオプションを用いて変更できる。
--param ssp-buffer-size=N

どの関数についてもスタック破壊検出コードを生成させるには次のオプションを使う。
-fstack-protector-all


● -ftrapv
符号付き整数同士の加算・減算・乗算で整数のオーバーフローをランタイムに検出する。
検出した場合はabortする。
除算はNビット同士の除算結果がNビットを超えないためチェック対象外である。

オーバーフローの有無をチェックする関数とabort関数が
ハードコーディングされる。



valgrindの使い方
valgrindを使うにはビルド時に以下3点を気にかけておかなければならない。

・デバッグオプションを付与する
・最適化は最大O1まで
・スタティックリンクは避ける

スタティックリンクは避ける理由は
valgrindは仮想的なCPU上でプログラムを実行するのだが、
mallocなどの関数は置き換えが不可能であるためである。

% g++ -g -O0 -o hoge.o hoge.c

一般的によく使うオプションはこのあたりだろう。
$ valgrind \
--leak-check=full \
--leak-resolution=high \
--show-reachable=yes \
--trace-children=yes
./hoge.o 

先に一度書いているが、stack、data、bss変数の検査は不可なので注意すること。



2014年6月18日水曜日

Cの静的ライブラリ、共有ライブラリの作成手順メモ


静的ライブラリと共有ライブラリの作り方の手順を残す。

まず、言葉の定義として、
静的ライブラリは、スタティックライブラリ(static library)と同義である。英語訳しただけだ。

共有ライブラリは、動的(ダイナミック)ライブラリ(dynamic library)とほぼ同じだが同義ではない。
共有ライブラリは動的にロードされるのだが、下の2つの場合がある。
1. ビルド時に読み込むライブラリ内オブジェクトをリンクし、実行時にそれを動的に読み込む場合
2. 実行時に初めて読み込むライブラリが決まり、実行時にリンクしてそれを動的に読み込む場合
前者のことを指す場合は、動的ライブラリ、
後者の場合は、動的リンク、
という言葉を用いることにする。

もう一点。動的にリンクされた実行ファイルと共有ライブラリをstatifierを使って
静的にまとめる場合などもあるかもしれないが、そういった場合はどう表現すべきだろうか。
その時は、そう書く。またここではその説明もしないので気にしない。


では本内容をまとめておく。
【静的ライブラリの作成】
【共有ライブラリの作成】
二つを比較すると違いが鮮明になり分かりやすいだろう。

共有ライブラリの作成中に一部理解しにくい部分があるだろう。
そこを次の章で補う。
【共有ライブラリの依存関係の確認】

さらにこれも試してみる。
【共有ライブラリの差し替え】

また、ビルド時には不明であった共有ライブラリを実行時に
リンクして動的に読み込ませる手段と、
この中での共有ライブラリの差し替え方法を記載しておく。
【動的リンクローダの利用】
【動的リンクローダ利用時の共有ライブラリの差し替え】


【静的ライブラリの作成】
ライブラリにする関数を作成する。
$ vi aaa.c
void aaa() {
  puts("aaa");
}
bbb.c も同様。

オブジェクトファイルを作るためにコンパイル(のみを行う)。
$ gcc -c -o aaa.o aaa.c
$ gcc -c -o bbb.o bbb.c

オブジェクトファイル群を一つのアーカイブにする。
$ ar rusv libccc.a aaa.o bbb.o
r : replace(既存なら置換、新規なら挿入)
u : update(タイムスタンプを見て新しいものを置換)
s : storeroom(書庫インデックスを作成しリンクが高速になる)
v : verbose(詳細モード)

できあがったアーカイブの中身を確認する。
$ ar tv libccc.a

静的ライブラリを利用する実行ファイルを作成する。
$ cat main.c
int main() {
 aaa();
 bbb();
 return 0;
}

コンパイル。
$ gcc -c -o main.o main.c

ライブラリをリンクする。
ライブラリの必要なオブジェクトファイルが実行コード中にコピーされる。
$ gcc -o main main.o -L. -lccc

下記でもよい。
$ gcc -o main main.o -L. libccc.a

実行できる。
$ ./main
aaa
bbb



【共有ライブラリの作成】
まずはオブジェクトファイルを作る。
共有ライブラリ用のオブジェクトファイルには、
位置独立コードである、PIC(Position Independent Code)を指定した方がよい。
(参考) Linux の共有ライブラリを作るとき PIC でコンパイルするのはなぜか

$ gcc -fPIC -c -o aaa.o aaa.c
$ gcc -fPIC -c -o bbb.o bbb.c

共有ライブラリはアーカイブファイルではなく、
一つのオブジェクトファイルとして作られる。
$ gcc -shared -o libccc.so aaa.o bbb.o

上記でいいのだが、sonameを考慮した共有ライブラリを今回は作る。
$ gcc -shared -Wl,-soname,libccc.so.0 -o libccc.so.0.0.0 aaa.o bbb.o
-Wlによってカンマで区切られたパラメータをリンカに渡せる。
soname(shared object name)が実行時にリンクする共有オブジェクト名になる。
つまり、libccc.so.0である。
ただし、実際に作成するオブジェクトのファイル名はlibccc.so.0.0.0とする。

そのため、シンボリックリンクを張っておく必要がある。
$ ln -s libccc.so.0.0.0 libccc.so.0

命名規則は一般的な規則でそろえるべできある。
互換性のあるマイナーバージョン、リリースバージョン時に管理がしやすい。
・soname名
lib + ライブラリ名 + .so + メジャーバージョン
・実際のファイル名
lib + ライブラリ名 + .so + メジャーバージョン + マイナーバージョン + リリースバージョン


共有ライブラリのリンクを実施する(静的ライブラリの場合と同じである)。
ただし、動作は大きく異なる。
共有オブジェクトのSONAMEを実行ファイルのNEEDEDに設定(あとで説明)するだけであり、
オブジェクト自体はコピーされない。
$ gcc -o main main.o -L. -lccc
または
$ gcc -o main main.o -L. libccc.so.0

動くだろうか。しかしこれでは動作しない。
$ ./main
./main: error while loading shared libraries: libccc.so.0: cannot open shared object file: No such file or directory
ダイナミックリンクローダがライブラリを見つけられないためである。

実行ファイルを実行するときに、動的リンカローダが必要な共有ライブラリを探し出し、
実行時に実行バイナリと同じプロセス空間で共有ライブラリを使えるように
メモリのマップを操作するためである。

一時的であれば環境変数を指定すればよい(上書きしないように念のため:で追記しておく)。
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

恒久的な対応をとる場合は、 /etc/ld.so.conf あたりにパスを書いて、
ldconfig で認識させればよい。

これで動くはずである。
$ ./main



【共有有ライブラリの依存関係の確認】
共有ライブラリの依存関係が意図通りになっているか確認しておこう。
※実行結果は一部略

実行ファイルやオブジェクトファイルのフォーマットを規定している
ELF(Executable and Linking Format)をのぞいてみる。
必要な共有ライブラリはELFの動的セクションのNEEDEDに記載がある。
このNEEDEDがSONAMEと一致している。
$ objdump -p main | grep NEEDED
NEEDED               libccc.so.0

$ readelf -d main | grep NEEDED
0x00000001 (NEEDED)   Shared library: [libccc.so.0]

ただし上の二つでは
ライブラリがその中で他のライブラリを必要としている場合、
また手動でたどっていかなければならず手間である。
また、SONAMEの実際のパスと割り当てられたメモリアドレスも確認できない。
そういった用途にはlddが便利である。
$ ldd main
libccc.so.0 => $HOME/lib/libccc.so.0 (0x00b4b000)



【共有ライブラリの差し替え】
共有ライブラリ内のオブジェクトよりも優先的に、
別の同名共有オブジェクトを使うようにすることもできる。

aaa()関数名は同じである。
内部では先に作ったものと区別がつくように処理が変わっている。
$ vi aaa_fake.c
void aaa() {
  puts("aaa_fake");
}

共有ライブラリを作り直す。
$ gcc -shared -fPIC -o aaa_fake.so aaa_fake.c

LD_RELOADを指定することで、このパスの共有オブジェクトが優先的に探索される。
$ export LD_PRELOAD=./aaa_fake.so

意図通り入れ替わっていることが分かる。
$ ./main
aaa_fake
bbb



【動的的リンクローダの利用】
ビルド時には不明であった共有ライブラリを実行時に動的に読み込ませることもできる。
共有ライブラリの作成の章で作ったライブラリをロードさせてみる。

dlopenで共有のライブラリのハンドルを得て、
dlsymにてハンドルからオブジェクトのシンボルを探索する。
dlopen内のRTLD_LAZYオプションはdlsymでシンボルを探索するまで
シンボルの解決をしないという意味である。
説明は最低限だが、あとはコードを見てもらった方が早いだろう。

(main.c)
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int main() {
  void *handle;
  int (*aaa) ();
  int (*bbb) ();

  handle = dlopen("./libccc.so", RTLD_LAZY);

  if (handle == NULL) {
    fprintf (stderr, "%s", dlerror() );
    exit (EXIT_FAILURE);
  }

  aaa = dlsym(handle, "aaa");
  bbb = dlsym(handle, "bbb");

  if (aaa == NULL || bbb == NULL) {
    fprintf(stderr, "%s\n", dlerror() );
    exit(EXIT_FAILURE);
  }

  aaa();
  bbb();

  dlclose(handle);

  exit(EXIT_SUCCESS);

  return 0;
}

動的ライブラリをロードするためのライブラリが必要なため、
-lでdlを別途読み込ませること。
$ gcc -o main main.c -ldl 
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
$ ./main
aaa
bbb



【動的的リンクローダ利用時の共有ライブラリの差し替え】
dlsym時にRTLD_DEFAULTを使うとディフォルトのライブラリ検索順番で、
RTLD_NEXTを使うとライブラリ検索順序の中で現在のライブラリ以降で
最初に関数が現れるところを探すよう指示てできる。
この機能を使うことで、別の共有ライブラリの 関数へのラッパーを提供することができる。

LD_PRELOADで指示する共有ライブラリ内から対象のオブジェクトを見つけられるか確認する。

// aaa = dlsym(handle, "aaa");
aaa = dlsym(RTLD_DEFAULT, "aaa");

またRTLD_DEFAULTやRTLD_NEXTはGNU拡張なので、
コードの最上段に以下を加えておく必要がある。

#define _GNU_SOURCE
#include <dlfcn.h>

これでビルドできるはずである。
$ gcc -o main main.c -ldl

LD_PRELOADはLD_LIBRARY_PATHよりも優先されるので、
RTLD_DEFAULTにすると、LD_PRELOADしたオブジェクトが、
RTLD_NEXTにすると、次の検索対象である、LD_LIBRARY_PATHからオブジェクトが探索される。
ここではdlsymにRTLD_DEFAULTを指定したのでどうなるかは想像できるだろう。
$ export LD_PRELOAD=./aaa_fake.so
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
$ ./main
aaa_fake
bbb


最後に、LD_PRELOADをunsetして、
元のlibccc.so.0内のaaa()を読むことも確認しておく。
$ unset LD_PRELOAD
$ gcc -o main main.c -ldl
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
$ ./main
しかし、aaa()が見えずに、エラーになる。
LD_LIBRARY_PATHへのパスは通しているにも関わらず、である。
少し悩んでしまったが、共有オブジェクトのSONAMEを
実行ファイルのNEEDEDに設定していないからである。

$ gcc -o main main.o -L. -lccc
または
$ gcc -o main main.o -L. libccc.so.0
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
$ ./main
aaa
bbb



『参考』

2013年12月15日日曜日

C言語でポリモーフィズム(polymorphism)を実現

C言語にはオブジェクト指向言語のようにオブジェクトに
メソッド(手続き)を持ち運ぶ手段は提供されていない。

ただし、関数ポインタを高階関数として利用すれば似たようなことはできる。
オブジェクト指向言語ではないので、ダブルディスパッチを用いて
目的を達すると言ったほうが適切かもしれない。

デザインパターンであるvisitor(ビジター)パターンは
オブジェクト指向言語でしばしば利用される。
しかしこれは決してJavaやC++だけで利用できるパターンではない。
これを非オブジェクト指向言語であるCで実装してみる。


visitor パターンとは
(wikipediaより)
オブジェクト指向プログラミング およびソフトウェア工学 において、
アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。
分離による実用的な結果として、既存のオブジェクトに対する新たな操作を
構造を変更せずに追加することができる。

さて、パターンの定義はややこしいが、意味と役割は非常に分かりやすい。
難しく考える必要はない。一言でこのパターンの利用用途を表すと下記になる。

visitする関数、visitされる関数がどちらも部品として取り替える必要がある場合に使う。
- visitorする側は、visit()メソッドを実装する。
- visitされる側は、accept(visitor)メソッドを実装する。


関数ポインタが不慣れな場合もあるだろう。
基礎から復習を始める。

◆ 関数ポインタについて
// int型を引数に取るvoid型を返す(何も返さない)、関数へのポインタを宣言
void (*fp)(int); // void (*fp)(); でよい

// 適当な関数を作成
void my_func(int n) {
printf("%d\n", n);
}

// 関数の先頭アドレスをポインタへ代入
fp = my_func; // &my_funcでもよい

// 呼び出し
(*fp)(10);



復習を終えたところでvisitorパターンのコードを書いてみる。

◆ サンプル例
#include <stdio.h>

void (*visit)();
void (*accept)(void (*visit)()); // void (*accept)();でよい


void accept1(void (*visit)()) {
puts("accept1");
(*visit)();
}

void accept2(void (*visit)()) {
puts("accept2");
(*visit)();
}

void visit1() {
puts("visit1");
}

void visit2() {
puts("visit2");
}


int main() {
accept = accept1;
visit = visit1;
accept(visit);

accept = accept2;
visit = visit2;
accept(visit);

return 0;
}


結果はこうなる。
accept1
visit1
accept2
visit2



関数ポインタのプロトタイプ宣言(typedef)をしておけば
可読性があがるだろうか。

◆ サンプル例(typedef版)
#include <stdio.h>

// 関数ポインタをtypedefで定義
typedef void (*visit_type)();

typedef void (*accept_type)(visit_type); // typedef void (*accept_type)();でよい


void accept1(visit_type visit) {
puts("accept1");
visit();
}

void accept2(visit_type visit) {
puts("accept2");
visit();
}

void visit1() {
puts("visit1");
}

void visit2() {
puts("visit2");
}


int main() {
// 型の変数を宣言
visit_type visit;
accept_type accept;

accept = accept1;
visit = visit1;
accept(visit);

accept = accept2;
visit = visit2;
accept(visit);

return 0;
}

2012年12月25日火曜日

Cによる2次元配列のメモリ管理を2つの実装で


Cで2次元配列のメモリの割り当てと解放を行うmallocとfreeを2つの方式で実装してみる。


◆ 実装1
● 概要
1次元配列に各配列のアドレスを保存し、
各配列ごとに新しく配列を作っていく。


● メモリの割り当てイメージ

□□□□□
□□□□□
□□□□□

下の方がイメージつきやすいか。
□□□□□
□□□□□
□□□□□


● コード ※例外処理は省略
int** my2dMalloc(int rows, int cols) {
int** rowptr;
int i;
rowptr = malloc(rows * sizeof(int *));
for(i = 0; i < rows; i++) {
rowptr[i] =malloc(cols * sizeof(int));
}
}

void my2dFree(int ** rowptr, int rows) {
int i;
for(i = 0; i < rows; i++) {
free(rowptr[i]);
}
free(rowptr);
}



◆ 実装2
● 概要
各配列のポインタを保存する配列と、
各配列を連続した1ブロックに割り当てる。


● メモリの割り当てイメージ
□□□□□ □□□□□ □□□□□


● コード ※例外処理は省略
int** my2dMalloc(int rows, int cols) {
int i;
int header = rows * sizeof(int*);
int data = rows * cols * sizeof(int);
int** rowptr = malloc(header + data);

int* buf = (int*) (rowptr + rows);
for(i = 0; i < rows; i++) {
rowptr[i] = buf + i * cols;
}
return rowptr;
}

void my2dFree(int ** rowptr) {
free(rowptr);
}


2012年10月13日土曜日

C++利用時のコーディング規約

C++を利用する時には自分なりのコーディング規約、ルールを決めておくべきである。
統一したポリシーを決めておかないと一人で書いていても、
コードの修正もリーディングも後々困難になる。
Google C++スタイルガイド 日本語訳も大変参考になる。
ここでは上記に含まれていない点を中心にまとめる。


◆◆◆ 命名規則
コード内の命名規則を決めておく。

● クラス名
最初の一文字目は大文字。

名詞を使う傾向がある。
ただし、抽象クラスは形容詞をつける傾向がある。
(例) Observable


● メソッド、関数名
動詞を当てはめる傾向がある。

Yes/Noのクエリの回答を行う場合は、
Is、Are、Hasといった接頭辞をつける。
(例) IsEnabled()、HssChildren()
否定系は使わない。
(例) IsUnabled()

メソッドではなく自由関数の場合は、
アクションを適用する対象のオブジェクト名を含める。
(例) FileOpen()


● クラス内のメンバ(フィールド)名
"m" + 頭だけ大文字の一文字
(例) mVar


● 配列変数名
配列名は複数形にする。

【例】
book → books
child → children

newした場合にdeleteなのか、delete[]なのかで間違いが起こりにくい。


● ヘッダで利用するインクルードガード名
大文字で記す
#include HOGE_EXAMPLE

名前異空間を使っている場合とそうではない場合には気をつけること。
Hoge::Example
HogeExample

それぞれを区別できるようにしておく。
#include HOGE_EXAMPLE
#include HOGEEXAMPLE
とする。



◆◆◆ 用意しておきたいマクロ
● 0ポインタ
deleteする場合は必ず0を代入する。
目的は、deleteしたポインタを間違って利用しないためである。
0にしておけばアクセスした瞬間に落ちる。
他の値では無効な値でも動き続ける可能性がある。
デストラクタ内であれば変数は後処理され見えなくなるため不要ではと反論もあるだろう。
delete後もnewによって再利用され、new時点での値が残っている可能性はある。

#define SAFE_DELETE(x) { delete (x); (x) = 0; }

改行を入れるならこうだ。
# define SAFE_DELETE(x) {\
  delete(x);\
  (x) = 0;\
}

配列版も用意する。
#define SAFE_DELETE_ARRAY(x) { delete[] (x); (x) = 0; }

上記を適当なヘッダファイルに記載しておき、
利用するccファイルでincludeしてやればいいだろう。

利用例

T** array = new T*[n];
for (int i=0; i<n; ++i) {
  array[i] = new T();
}

for (int i=0; i<n; ++i) {
  SAFE_DELETE(array[i]);
}

SAFE_DELETE_ARRAY(array);



◆◆◆ new/deleteポリシー
newされた変数はそのクラスが責任を持って
デストラクタ時にdeleteする。

T::~T() {
  SAFE_DELETE(mHoge1);
}

newしたものを渡すときは例外とし、
渡された側がdeleteする。またその旨をコメントしておく。



◆◆◆ メンバ初期化
コンストラクタ時にはメンバをすべて初期化(メンバイニシャライザ)する。
メンバイニシャライザの方が、普通に代入によって初期値を与えるよりも効率的であるためである。
すべての変数の初期化が手間であっても最低限、ポインタ変数は必ず実施し、初期化されていないことによる不可解なバグを回避する。

T::T():
  mX(0),
  mY(0),
  mZ(0) {
  ~
};


例えば下でinitを呼ぶ前にset()を呼んだらどうなるか。
mXは不定である。

class T {
  T() {}
  void init() {
    mX = new int();
  }
  void set(int a) {
    *mX = a;
  }
};

初期値を()で囲むのは、各メンバのコンストラクタを呼んでいるためである。
つまり()の中の値は、コンストラクタに引数を与えていることになる。
int型などの言語に標準で用意されている型(プリミティブ型・基本型などと呼ぶ)であっても、
コンストラクタが存在しているということが推測できる。



◆◆◆ 継承を使う場合の基本設計
● どこで使うか
以下のメリットがある場合に限る。
① 共通部分のくくりだしにより抽象化された全体の性質を見せる時。
 例えば、人というスーパークラス(基底)があれば、
 男や女のサブクラス(派生)の性差の違いは別にして男や女が
 ある程度イメージできる。

② サブクラス(派生)とスーパークラス(基底)がis-aの関係になる時。
 共通部分のくくりだしだけに注目して継承関係を結んではならない。
 
 ただし、is-aの関係にあっても、
 リスコフの置換原則(LSP:Liskov Substitution Principle)には
 従はなければならない。
 LSPとは、SがTの派生型であれば、つまりこう、
  T(スーパークラス)
  S(サブクラス)
 T型のオブジェクトが使われている箇所は全て
 S型のオブジェクトで置換可能。

 以下のような継承関係はis-aであってもLSPに反する。
  T(スーパークラス): Ellipse
  S(サブクラス): Circle
 こういった場合は、合成を利用すべきである(holds-aの関係である)。
 合成では相手クラスのパブリックメンバのみに結合するだけである。

③ スーパークラスだけしか知らなくてもいい人に、サブクラスを隠せる時。
 人の機能を使いたい場合に男か女かどうかは知る必要はない、ということである。

④ 繰り返しを避けられる時。


● 継承時のトラブル対策
やってしまいがちなトラブルを防ぐためのポリシーを決める。
【概要】
① スパークラス(基底クラス)をnewさせない

② 継承時のdelete忘れ


【詳細】

Parentをスパークラス、Childがサブクラスとする

Parent *parent = new Child;
これはあっても、

Parent *parent = new Parent;
このような使い方はしない。
スーパークラスのポインタを通してサブクラスの関数を呼ぶために、
継承させているのである。

ただ、親側をnewすることを機械的に防ぎたい。

親側の関数を子側で再定義し使用する(オーバライドする)ことを
前提として仮想関数として利用する際に、
仮想化させるvirtual付の関数に=0をつけることもできる。
=0をつけた仮想関数は純粋仮想関数というのだが、クラスはnewできなくなる。
これでうっかりミスは防止できる。
1個でもこれがあるクラスを抽象クラスという。

class Parent {
  public:
  virtual void play() = 0; //中身は持たせない
};



スーパークラスのデストラクタにvirtualを付与して仮想関数にして、
継承時のdelete忘れを防止する。
ただし、デストラクタは=0をつけない(純仮想関数にはしない)。

失敗例
Parent* parent = new Child;
delete parent;

Parentのデストラクタしか呼ばれない。
Child内でnewしていた場合は、
Childのデストラクタも呼ばなければならない。

そこで、デストラクタも仮想関数にしてやる。
class Parent {
  public:
  virtual void play() = 0;
  virtual ~Parent() = default;
};
デストラクタに0をつけないのは、親側のデストラクタは中身を持つからである。
スーパークラスのデストラクタにvirtualは必須とすればよい。
子側のデストラクタを呼ぶと親側のデストラクタも呼ばれる。



◆◆◆ 内部でnewするクラスの基本設計
内部でnewするクラスの実体のコピーを防止するために、
コピーコンストラクタと代入演算子は禁止する。
実体を引数にする場合、STLコンテナに入れる場合(これに関しては別観点で後述する)はどうすればよいか。
その時はポインタを使う。

class T {
  private:
  T(const T&); // コピーコンストラクタの禁止
  void operator=(const T&); // 代入演算子の禁止

  public:
  T() {mX = new int();}
  ~T() {delete mX;}
};

しかしながらクラスごとに記載するのは手間がかかる。
以下のようなマクロをprivate内で使うと便利である。

#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
  TypeName(const TypeName&);               \
  void operator=(const TypeName&)

class T {
 public:
  T(int f);
  ~T();

 private:
  DISALLOW_COPY_AND_ASSIGN(T);
};



◆◆◆ ポインタ配列と実体配列の使い分け
【どちらを使うべきか】
① ポインタを入れる場合(普通はこうだろう)
T** array = new T*[n];


② 実体を入れる場合
T* array = new T[n];


【性能面からの比較】
①では1個ずつnewしなくてはならない。
毎回newし、その数だけdeleteも必要になってくる。
T** array = new T*[n];
for (int i=0; i<n; ++i) {
  array[i] = new T();
}

for (int i=0; i<n; ++i) {
  SAFE_DELETE(array[i]);
}

SAFE_DELETE_ARRAY(array);


②ではnewは一度だけでよい。
T* array = new T[n];
for(int i=0; i<n; ++i) {
  array[i].initialize();
}

SAFE_DELETE_ARRAY();

ディフォルトのコンストラクタしか呼び出せないため、
initialize()というような(名前はなんでもよい)初期化用の
メソッドを作り読みこませる必要はある。
コンストラクタとは別に初期化関数を呼ぶ手間がかかるが、
newの回数も減り破棄するコードも短くなる。


性能面だけを考えるなら、②を使うべきだろうか。


【安全面からの比較】
ただし下のようなコードを書く懸念があることに留意が必要である。
STLの変数として実体を使うコードである。

class T {
  A() {mX = new int();}
  ~A() {delete mX;}
};

vector<T> gT;

void add() {
  T t;
  gT.push_back(t);
}

int main() {
  add();
  return 0;
}

問題
add関数内でのローカル変数としてt作成時にクラスTのコンストラクタが走る。
push_backはコピーコンストラクタが走る。
クラスTのコピーが走る。
明示的にコピーコンストラクタを宣言しなかった場合はコンパイラが暗黙のうちに
コピーコンストラクタを生成している。これをデフォルトコピーコンストラクタと呼ぶのだが、
単にクラスのメンバをそのままコピーするだけである。
つまりメンバ変数mXもコピーされる。このポインタ変数がコピーされてしまうと
同じ場所を指すポインタが、コピー元とコピー先とで2つ存在することになる。
add終了時にローカル変数として作ったtのdeleteが走り、コピー元が消え、
コピー先でもあるmXも使えなくなる。

簡単なコードでもこういったトラブルは起きる。
性能を犠牲にしても安全面を優先するという選択も判断材料にしてほしい。

ちなみにこの対策としてすぐに2つ思いつく。
・中でnewする型はSTLコンテナへはポインタを入れる

・内部でnewするクラスはコピーコンストラクタと代入演算子は禁止
 ◆◆◆ 内部でnewするクラスの基本設計で実施した通りである。



◆◆◆ 関数のパラメータは値かポインタか参照か
値渡しの場合はいいだろう。
パフォーマンスの懸念がなく書きかえもしないようなデータを渡す時である。

考慮が必要なのはポインタか参照かということになる。
実質両者は同じものであるが、
参照にメリットがあるとすれば、
構文がシンプルになる、
NULLにならない、よってNULL値チェックがいらない、
といったメリットがあるだろうか。

以下を見てみる。どの変数が書き換えられるか分かるだろうか。
func(a, b, c, d, e, f);

書き換えない変数はconstの付与を徹底すればいいのではと思うだろう。
func(型* a, 型* b, 型* c, const 型& d, const 型& e, const 型& f)

しかし、それでは関数内で利用する関数で分からなくなる。
func1(型* a, 型* b, 型* c, const 型& d, const 型& e, const 型& f) {
 func2(a, c, e);
 func3(b, d, f);
}

ヘッダを見ればよいだろうが、それではヘッダを見る手間がかかる。

そこで、書き換えないなら参照(もちろんconstをつければなおよし)、
書き換える場合はポインタ渡し、
というルールにしてもいいかもしれない。
func(&a, &b, &c, d, e, f);
dとeとfは入力であり、aとbとcが出力であることが一目瞭然である。

ただしこれでは管理工数がかかると思うかもしれない。
関数の引数は書き換えられる可能性があると常に頭に入れておく、
ぐらいの緩いポリシーの方がいい場合もあるだろう。



◆◆◆ 関数、メソッドからの戻り値に関して
関数、メソッドからの戻り値にポインタか参照、どちらを使うべきだろうか。

まずはポインタ返しと参照返しの例を見てもらう。

① ポインタを使う場合
class T {
  public:
    A* getA() const { return &a; }
  private
    A a;
};

A *a = t.getA();


② 参照を使う場合
class T {
  public:
    A& getA() const { return a; }
  private
    A a;
};

A& a = t.getA();
もしくは
A a = t.getA();

ただし、後者は代入時に値のコピーが走る。
Aが大きなクラスならそのコストがかかる。
知らないうちに遅いコードを書いていたということが起きるかもしれない。

①の場合にコピーが発生する場合は、こういったコードを書いたときだろう。
A a = *t.getA();
関節参照演算子である*を使うことで
その指している先が利用されることが明示的にわかる。

結論だが、どちらでもいい気がするが、
*をつける手間はあるが、何をしているのかはっきりする
①の方針をとることが多い。



◆◆◆ ヘッダのポリシ
【概要】
① インクルードガードの付与

② using namespaceの不宣言

③ パブリックなヘッダとプライベートなヘッダの使い分け

④ すべてのヘッダは自分が依存している他のヘッダファイルをインクルード

⑤ ccファイルからのインクルードは必要なものだけをインクルード

⑥ ヘッダのパス名のルール


【詳細】
① インクルードガードの付与
インクルードガードは基本だろう。

// example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H

class Example {
   ・
   ・
   ・
};

#endif


② using namespaceの不宣言
usingキーワードを使うことで名前空間内でシンボルを簡単に使うことが可能となる。
using namespace std;
stdライブラリであれば、std::coutをcoutと略して使える。
Hogeというnamespaceにいれば、
Hoge::cout
そうではければグローバルな名前空間として
::cout
と区別をつけることはできる。

ただし、ヘッダ中でusing namespace宣言を多用すると、
これを参照したファイルもグローバル名前空間に見えてしまい、
名前空間の意味がなくなってしまう。
特にAPIとして提供するようなパブリックヘッダでは厳禁とする。

プレーンなCでよくやるように、一意な接頭辞を使う方法もあるだろう。


③ パブリックなヘッダとプライベートなヘッダの使い分け
パブリックなヘッダはクラスの顔であるべきであり、内部事情をさらさない。
コンパイラにとって優しいだけではなく、人にとってもその方がいいからである。


④ すべてのヘッダは自分が依存している他のヘッダファイルをインクルード
b.hで定義されている型をa.hで使っていた場合、a.hの冒頭で下を書く。
#include "b.h"
そうしないと、a.hをインクルードする側に、b.hのインクルードも強要することになり、
管理が煩雑になる。

a.hを使うccファイル側に下のようなことはしたくない。
#include "a.h"
#include "b.h"


そもそもインクルードが不要で前方宣言だけでいい場合がある。

クラスAがクラスBのヘッダのインクルードが不要となる例を見てみる。
まず、クラスBを前方宣言。
class B;

・変数がポインタ
class A {
  B* b;
};

・変数の実体を関数の引数に使う
class A {
  void setB(B);
};

・変数の実体を返り値に使う
class A {
  B getB();
};

・テンプレートを使う場合
class A {
  Hoge<B> b;
};

クラスAがクラスBのヘッダを必要とするのは、
そのクラスのオブジェクトをデータメンバとして利用する場合、
そのクラスから継承する場合のみである、ということである。

⑤ ccファイルからのインクルードは必要なものだけをインクルード
④を徹底していれば特に気にすることはない。必要なものをインクルードすればよい。


⑥ ヘッダのパス名のルール
ディレクトリを掘った場所にあるヘッダをccファイルから読み込む場合は
インクルードパスからのパス名も記載する。
#include "hoge/piyo/example.h"

ccと同じディレクトリにあれば当然パスは不要である
しかし規則を統一させたいためこうする

また同じ名前のヘッダ名があった場合に間違いを防げる



◆◆◆ キャスト
型変換するキャスト時にはC++で用意されている型変換方法をとる。
つまり、下のようにはしないということである。
float a = (int)b;

どういうキャストなのか明示させる目的と、
検索しやすくするためである。

【概要】
① static_cast
 intやfloatなどの相互変換。

② reinterpret_cast
 ポインタ型同士の変換や、int型の変数をポインタ型に変換する場合など。
 利用するのはバイナリを読み書きする時ぐらいだろうか。

③ dynamic_cast
 ダウンキャスト時。

④ const_cast
 constを外すような設定はしない。
 忘れてよい。


【例】

int num;
double f = 12.3;
num = static_cast<int>(f);


クラスTがあったとして、intの数字をポインタに変換する。

int num = 12345;
T* p = reinterpret_cast<T*>( num );

当然だが、どこを指すか分からない値をアドレスとして使うことはできない。

このキャストはバイナリの読み書きぐらい時にしか使わない。

いい例を見かけたのでそれを用いて説明する。
ある画像データには12バイト目から4バイトに画像の高さ情報が格納されている。
この画像フォーマットでは高さを表す型はunsignedと決まっている。
unsigned *height;

画像データを1バイトずつ読みたいためchar型のimageData変数に取り込む。
char* imageData;

この画像データから高さ情報を抜き出してみる。
height = reinterpret_cast<unsigned*>(&imageData[12]);

しかしこれではまずい点が2つある。
heightのunsigned型が4で割り切れるアドレスを持っている保障はない。
ある変数を最初からunsigned型とした作った場合はコンパイラが4で割れる場所に置いてくれる。
char型はその限りではない。仮に偶然char型が4で割れる場所に格納されても、
例えば勝手に7文字分目からを読んでunsignedに変換したらそれは4では割り切れないだろう。

10進数で言えば、1234という数字はそのままでは解釈できないため、
1と2と3と4に分けて読んで合成することになる。
4 + 3*10 + 2*100 + 1*4000 = 1234 // リトルエンディアン例
1 + 2*10 + 3*100 + 4*4000 = 4321 // ビッグエンディアン例

2つの例を書いたのは、合成するにあたって
エンディアンがリトルエンディアンなのかビッグエンディアンなのか
考慮しなければならないためである。


ここでの例として使った画像データがリトルエンディアンの場合は
下のように書き換えるべきである。

コンピュータは8ビットをまとめたバイト単位でしか読み書きできない。
ただ、実際は4バイト単位で操作する。
2の8乗である256進数の数字が4つ並んでいるイメージである。

unsigned height = getUnsigned(&imageData[12]);

unsigned getUnsigned(const char+ p) {
  const unsigned char* up;
  up = reinterpret_cast<const unsigned char*>(p)
  unsigned ret;
  ret =  up[0]
  ret |= (up[1] << 8);
  ret |= (up[2] << 16);
  ret |= (up[3] << 24);
  return ret;
}

②の説明が長くなった。
③、④は割愛。



◆◆◆ unsignedの使いどころ
正の整数であればunsignedでいいのだろうか。
その変数が正であっても減算処理内で計算結果が机上で負数にならないことを
考慮できればよいがそれを考慮するのも手間である。

そこで次の指針を立てる。

① ビット演算をする場合

② intでは足りない範囲で必ず正になる計算処理をする場合
 上を考えたくなければすべてintでいい。
 または富豪的プログラミングをするのであればdoubleを使う。



◆◆◆ インライン化の方針
コンパイル時に関数の呼び出し側に関数をコピーし、
呼び出しのコストを抑えるというメリットがあるが基本使わない。

理由はインライン化するには関数の中身が見えなければならず、
同一ccファイルでしか使えないため利用用途が限られる。
ccファイルから別のccファイルをインクルードなどしない。
コンパイルはccファイル頃にバラバラに行うのが普通だ。


ヘッダに関数の中身を書く方法はどうだろうか。
ヘッダに書かれた関数は自動でインライン化される。
しかし、ヘッダ内に関数の中身は書くことも推奨し難い。
利用者に不要な中身を見せない、という方針に反する。
またその関数を変更した場合、このヘッダを読み込む全ccファイルのコンパイルが走る。

また、virtualがつく仮想関数では使えない。
実行するまで分からないので仮想なのだから当然だ。

上記デメリットを上回るなら利用してもいいだろうが、
人が考えるよりコンパイラの最適化に任せた方が楽だろう。



◆◆◆ グローバル変数を利用しない
例えば、あるクラスのインスタンスをグローバル変数に代入して
使いたくはないが、引数に延々と渡していくようなこともしたくない。
そこでシングルトンを使う。
あるクラスのインスタンスがプログラム全体で
1つしか存在しないことも保証できる。

※シングルトンをモノステートと誤解ないように。
 シングルトン:1つしかインスタンスを作成させないことによる単一構造の強制
 モノステート:複数のインスタンスで同じデータを強要する単一動作の強制


class T {
public:
  // 唯一のアクセス経路
  // static関数として定義する
  static T* getInstance() {
    return msInstance;
  }

  // create()内でコンストラクト
  static void create() {
    if( msInstance == 0 ) {
      msInstance = new T();
    }
  }

  // destroy()内でデストラクト
  static void destroy() {
      delete msInstance;
    }

  void example() {
    ~
  }

private:
  // コンストラクタを隠す
  // 勝手にnewさせない
  // create()を通すことを強制する
  T() {}

  // デストラクタを隠す
  // destroyすればdeleteできるが、
  // 意図せずdeleteされない備えにはなる
  ~T() {}

  // コピーコンストラクタを隠す
  T( const T& );

  // 代入を隠す
  T& operator=( const T& );

private:
    static T* msInstance;
};


使い方もまとめておく。

クラスポインタであるmInstance変数を作る。
static指定子をつけた静的メンバ変数なので、
メモリ上にただ1つしか存在しない。
T::create();

メソッドを呼び出してみる。
T::getInstance()->example()

少し長く感じるなら別名をつければよい。
T *t = T::getInstance()
t->example();

破棄する場合はdeleteではない。
t->destroy();


余談だが、グローバル変数をファイル間で使っている場合、
その変数をexternして実体は別にいることを宣言することがある。
extern指定子をグローバル変数以外に使うことはない。
つまりシングルトンを利用すればexternは不要である。



◆◆◆ 煩雑なif文の忌避
C++のコードに限らないが、煩雑なif文は避ける。
【変更前】

if ((A == ~ && B == ~) || (B == ~ && C == ~) {

}


【変更後】
book isA = (A == ~);
book isB = (B == ~);
book isC = (C == ~);
bool condtion0 = (isA && isB);
bool condtion1 = (isB && isC);

if (condition0 || condition1) {

}



◆◆◆ 配置new(placement new)の利用
大量にnewしなければならず、速度も求められる場合には
メモリプールを使ってもいいだろう。

T* memoryPool = operator new(sizeof(T) * n);

for (int i=0; i<n; ++i){
  new( &memoryPool[i] ) T; // 配置new
}

for(int i=0; i<n; ++i) {
  array[i].~T(); // デストラクタの呼び出し(ここではメモリ解放は不要)
}

operator delete(memoryPool)

例えば、newはclass内にも定義できるので、
そこから配置newを使う場合は、グローバルスコープの配置newが確実に
呼ばれるように、スコープ解決演算子を付与してもいいかもしれない。
::new( &memoryPool[i] ) T;



◆◆◆ シングル引数を取るコンストラクタの宣言時のexplicitキーワード
C obj = 10;

C obj(10);
に変換される。

C::operator=(int)
が呼び出されるのではない。operator=()が定義してあっても同様である。

非明示的な振る舞いは混乱を招く。
非直感的な自動変換を禁止するため、
explicit C(int)
と宣言する。