リーダブルコード
2024年11月30日読了
Credit
Scott Meyers. (2005). Effective C++: 55 Specific Ways to Improve Your Programs and Designs. Addison-Wesley Professional. 小林健一郎訳. (2006). Effective C++ 第3版 ―プログラムとデザインを改良するための55項目. 丸善出版.
Summary
C++プログラムを効率化するテクニックを解説した本。
というよりは、C++の自由度ゆえの多々ある選択肢のうちどれが効率的かを示す本。
Note
- ポリモーフィズムのための基底クラスのデストラクタには
virtual
を付す。同様に、仮想関数を持つクラスのデストラクタにもvirtual
を付す。 - コンストラクタやデストラクタから自身の仮想関数を呼び出すまじ。
Date(int month, int day, int year)
ではなく、explicit Month(int m)
等を導入して、Date(const Month &month, const Day &day, const Year &year)
のようにする。なるほど、型で制限するという考え方もあるな。- 参照で返したくて仕方がないあまり、スタック上のオブジェクトの参照を返してしまわないように注意せよ。
- privateメンバにアクセスする必要がないならば、メンバ関数ではなく関数として定義せよ。なぜなら、privateメンバにアクセスする関数の数を減らせるから。
- stdを拡張したいなら次のようにする。
/* Fooがテンプレートを持たない場合 */
class Foo {
private:
// コピーのコストを下げるために内容をインスタンスとして持つ。
FooContent *_content;
public:
void swap(Foo &other) {
// シンボル検索範囲にstdを追加する。
// NOTE: ひょっとしたらFooContentに特化したswap()があるかもしれないため、
// std::swap(...)のように決め打ちしない。
using std::swap;
swap(_content, other._content);
}
};
namespace std {
// Fooに特化したstd::swap()
template<>
void Swap<Foo>(Foo &a, Foo &b) {
a.swap(b);
}
}
- stdには部分的に特化した関数を追加できない。
/* Fooがテンプレートを持つ場合 */
template<typename T>
class Foo {
private:
T *_content;
public:
void swap(Foo<T> &other) {
using std::swap;
swap(_content, other._content);
}
};
// Foo<T>に特化したswap()
// NOTE: stdは部分的に特化した関数を許容しないためstd外に定義する。
template<typename T>
void swap(Foo<T> &a, Foo<T> &b) {
a.swap(b);
}
void f() {
using std::swap;
...
swap(foo1, foo2);
}
- 例外安全な関数は次のうち1個を保証しなければならない:
- 関数が例外に対し基本保証をする:例外が投げられてもプログラム中のすべての状態が有効に保たれる。
- 関数が例外に対し強い保証をする:例外が投げられても、プログラムの状態は、その関数が呼び出される前の状態に保たれる。
- 関数が例外に対し投げない保証をする:必ず予定の仕事を実行し、例外を投げない。
- 基底クラスに適用できるものはすべて派生クラスにも適用できるようにしなければならない。
- ペンギンは鳥だからと言ってペンギンは鳥をpublic継承してはならない。ペンギンは飛べない。
- 正方形は長方形だからと言って正方形は長方形をpublic継承してはならない。正方形は幅だけを広げられない。
- 基底クラスのものと同じシンボルはオーバーロードされたもの含めてすべて継承先に隠蔽される。
- private継承はis-implemented-in-terms-ofの関係。つまり、概念的繋がりはないが実装的な繋がりのある関係。
- 例えば、継承すると有用だが概念的繋がりのない抽象クラスを使いたいとき、private継承する。ただし、もしその具体クラスがあり・かつ使えそうなら、それをコンポジションで持つ方が好ましい。
- テンプレート定義の
typename
とclass
とに違いはない。しかし、ことtypename
キーワードについて見ると、ネストされた依存型を使うために必要となる。
template<typename T> // typenameでもclassでも良い
class D : public A<T>::B { // typename不要
public:
D(int a): A<T>::B(a) { // typename不要
typename A<T>::C c; // typename必要
typename T::E e; // typename必要
}
};
- クラステンプレ―トを基底クラスに持つとそのメンバ関数は単には呼べない。
template<typename T>
class A {
public:
void f();
};
template<typename T>
class D : public A<T> {
public:
using A<T>::f; // (2) ここでusingしておく。
void g() {
f(); // (1) 確かにfはAのメンバだがA<T>は未定なクラスなのでコンパイルエラー。
// (2) usingしておけばOK。
this->f(); // (3) OK。
A<T>::f(); // (4) OK。
// ただし仮想関数の動的結合の観点で良くないらしい。
}
};
template<typename T, std::size_t n> class Matrix
とせず、n
をコンストラクタに取れば良いじゃんと一瞬思ったが、別のサイズの行列を別の型として取った方が当然ながら有用。- C++にもトレイト境界があれば良いのに。
- TMP (Template Meta Programming)面白い。次は繰返し(再帰)の例。
#include <iostream>
template<unsigned n>
struct Factorial {
enum { value = n * Factorial<n-1>::value };
};
template<>
struct Factorial<0> {
enum { value = 1 };
};
int main() {
std::cout << Factorial<20>::value << std::endl; // 2192834560
}
Impression
本書が発刊されたより後のC++を知っているため所々「古いなあ」と感じる。が、学びがないかと言えば当然そんなことはなく、上記の色々を学んだ。
本書はC++の罠の解説であって、あるいは悪く言えば小手先のテクニック解説であって、C++のパラダイムにおける設計のようなマクロな話の解説はない。つまり、マクロな話が分かる人がC++流の書き方を覚えるための本であろう。その点でも「中級者向け」という世間の評価は頷ける……いや、初心者に設計の話をするのも違うか。
■