Dive into Effective C++

C++やってますと言えるようになるために

Effective C++ 第3版 スコット・メイヤーズ著 小林健一郎訳

を読んでみる。

2008年くらいの書物なのでC++11対応はしていない。C++11についても色々知りたいが今はあまり背伸びしないで下記の記事で満足することにする。

C++11とは (シープラスプラスイレブンとは) [単語記事] - ニコニコ大百科

Introduction

イントロダクションなんて適当に読もうと思ったところ、そういうわけにもいかないようだ。。。。

/* 宣言 */
extern int x;
std::size_t numDigits(int number);
class Widget;
template<typename T>
class GraphNode;
// extern宣言
// * 外部の変数にアクセスできるキーワード

// size_t
// * 何かを数えるときに使う「符号なし整数」をtypedefしたもの


/* 定義 */
std::size_t numDigits(int number)
{
	std::size_t digitsSoFar = 1;
	while ((number /= 10) != 0)
		++digitsSoFar;
	return digitsSoFar;
}
template<typename T>
class GraphNode
{
	public:
		GraphNode();
		~GraphNode();
};


/* デフォルトコンストラクタ */
class A
{
	public:
		A();	// デフォルトコンストラクタ
};
class B
{
	public:
		explicit B(int x=0, bool b=true)	// デフォルトコンストラクタ
		{
		}
};
class C
{
	public:
		explicit C(int x);	// デフォルトコンストラクタではない
};


/* explicitキーワード */
void doSomething(B bObject)
{
	std::cout << "B型のオブジェクトを引数に取る関数";
}
B bObj1;
B bObj2(28);		// int値の28からB型のオブジェクトを生成
doSomething(28);	// エラー!: intからB型オブジェクトへの暗黙の型変換はされない
doSomething(B(28));	// 問題ない: B型のオブジェクトを使ってintをBに変換している
// explicit
// * 暗黙的な型変換を禁止するキーワード
// * explicitを付けないコンストラクタだと予想外の型変換を起こす可能性がある
// * 下記のコードもexplicitを付けないと暗黙的に型変換をしてしまいエラーにならず通ってしまう
// * 特に理由がなければ、暗黙的な型変換をしないようにコンストラクタはexplicit付きで宣言する

/* コピーコンストラクタ */
class Widget
{
	public:
		Widget();								// デフォルトコンストラクタ
		Widget(const Widget& rhs);				// コピーコンストラクタ
		Widget& operator=(const Widget& rhs);	// コピー代入演算子
		~Widget();
};
Widget w1;		// デフォルトコンストラクタが使われる
Widget w2(w1);	// コピーコンストラクタが使われる
w1 = w2;		// コピー代入演算子が使われる
Widget w3=w2;	// コピーコンストラクタが使われる! => Widget w2(w1)とWidget w2=w1は同じ動作
bool hasAcceptableQuery(Widget w);
Widget aWidget;
if (hasAcceptableQuery(Widget w))	// コピーコンストラクタが実行
{
	// ユーザー定義型を値渡しするのは良くない。一般にはconst参照渡しが良い。
}
// コピーコンストラクタ
// * 同じ型の別のオブジェクトで初期化するときに使う
// コピー演算子
// * 同じ型の別のオブジェクトを代入するときに使う

/* 未定義な動作 */
int* p=0;				// pがヌルポインタ
std::cout << *p;		// ヌルポインタが示す場所を取り出そうとすると動作は未定義になる
char name[] = "Hoge";	// 要素数は6
char c = name[10];		// 無効な添字の要素にアクセスしようとすると動作は未定義になる
// undefined
// * C++ではこのような未定義な動作をかけてしまう(JavaやC#ではない)
// * C++では未定義な動作をするコードをできるだけ書かないようにする注意が必要


/* シグネチャ */
// * メソッドや関数の名前、戻り値、引数の型などの組み合わせのこと


/* インターフェース */
// * JavaやC#にあるInterface宣言とは違う使い方をしてます
// * 関数のシグネチャやクラスのアクセス可能な要素などを意味する
// * テンプレート型のパラメータを意味することもある


/* 命名法 */
// わかり易い名前を心がける
// * 略記について
const Rational operator*(const Rational& lhs, const Rational& rhs);	// left-hand sideとright-hand sideのこと
Widget* pw;	// ポインタ変数には接頭語に"p"をつける
class GameCharacter;
GameCharacter* pgc;
GameCharactor::move()
{
	mf = "hoge";	// メンバ変数のことをmfと書くこともある
}

#1 C++を複数の言語の連合と見なそう

C++のサブセット

  • c
  • オブジェクト指向C++
  • テンプレートC++
  • STL

C++で効率よくプログラミングするためのルールは、C++のどの部分を使うかで変わってくる。

#2 #defineより、const, enum, inlineを使おう

「プリプロセッサよりコンパイラを使おう」

プリプロセッサ
コンパイル作業において、初期段階で#define, #includeなどの内容が処理される。 その処理を行う機能(ツールなど)をプリプロセッサという。 広い意味ではコンパイルに含まれるが、狭い意味のコンパイルよりも後に実行される。
/* constを使う1 マクロの欠点 */
// この使い方をするとコンパイラからはASPECT_RATIOは見えない
// 1.653という数字になってる
// デバックするときに困るかもしれない
#define ASPECT_RATIO 1.653

// マクロを定数に置き換えることで解決
const double AspectRatio = 1.653;

// 利点
// * マクロを使うと、ASPECT_RATIOが出てくるたびに機械的にそれを1.653に置き換える(コピーが大量発生)
// * 一方、AspectRatioは常に1つしか存在しない


/* constを使う2 定数ポインタ */
// char*を使った定数文字列
// constが2回必要(charとポインタの2回)
const char* const authorName = "Nanashino Gonbe";
// 一般にはchar*よりstringを使ったほうが良い
const std::string authorName("Nanashino Gonbe");


/* constを使う3 クラスの持つ定数 */
// クラスで使う変数のスコープを限定するために、その定数をクラスのメンバにすることがある
// #defineでは再現不可能
// その定数が実際に1つだけ存在するようにstaticにする必要がある
class GamePlayer
{
	private:
		static const int NumTurns = 5;
		int scores[NumTurns];
};
// 問題
// * staticな整数型定数メンバであっても宣言時に初期化できない(昔のコンパイラを使っているならば)
class CostEstimate
{
	private:
		static const double FudgeFactor;	// ヘッダーファイルに書かれる
};
const double CostEstimate::FudgeFactor = 1.35;	// ソースダイルに書かれる
// 更に問題
// * GamePlayerのようにコンパイル時に「クラスの定数」が必要になった時、コンパイルエラーが出る。


/* enumハック */
// 上記の更に問題を解決する手法
class GamePlayer
{
	private:
		enum { NumTurns=5 };	// [enumハック] => NumTurnsを5として使える
								// constよりdefineに近い
								// enumハックは色々なところで利用されているので覚えておく価値有り!
		int score[NumTurns];	// 問題なし
};

/* マクロの問題点 */
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
// このマクロの悲惨さ
// * 仮引数を括弧で囲う必要がある
// * int a=5, b=0;
// * CALL_WITH_MAX(++a, b);		// aが2回インクリメント
// * CALL_WITH_MAX(++a, b+10);	// aが1回インクリメント
// * 運悪く引数によって挙動が変わる事がある
//
// こんなマクロを作るくらいならインライン関数のテンプレートを作る!
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
	f(a > b ? a : b)
}

単純な定数には、#defineよりもconstかenumを使うようにする。

#defineで定義されているマクロよりも、インライン関数を使おう。

※インライン関数については#30を参考に。