暗黙的な型変換に注意しようという話。
暗黙的な型変換には、3とおりの方法がある。
- 引数を一つだけ取るコンストラクタ、あるいは
引数に対してデフォルトの値を設定しているコンストラクタ
operator sometype() const みたいな型変換演算子
しかし、暗黙的な型変換は予期せぬ振る舞いに発展する可能性があるので、できれば避けたほうがよい。あなたの意図したものと、コンパイラが解釈した結果は異なる場合があるからだ。
型変換演算子
たとえば、ペットショップの犬を表すクラスを考える。
それぞれの犬にはIDが振られている。
class Dog {
public:
void bark();
operator int() const { return id_; }
private:
int id_;
char *name_;
};
いま、犬の名前を表示しようとして、うっかりDogクラスには
operator char*()がないことを忘れているとすると、
Dog pochi;
std::cout << pochi << std::endl;
なんてやってしまう。結果は、pochiのIDが表示されるというもの。
経験を積んだC++プログラマは、たいてい型変換演算子を避ける。
たとえば、std::stringにはoperator char*()の代わりに
c_str()が存在する。
引数を一つだけ取るコンストラクタ
こいつはよりたちがわるい。
ふつうにクラスを設計していても、こうしたコンストラクタを記述することはままある。そして、こうしたコンストラクタも、暗黙的な型変換に一役買ってしまうことがあり得る。
class Array {
public:
Array(int n); // n個の要素で初期化するコンストラクタ
};
Array a(10);
Array b(10);
for (int i=0; i<10; i++) {
if (a == b[i]) { // ここでミスしている !
...
}
}
なんて風に、a[i] == b[i]と書くべきだったところを書き間違えても、コンパイラは文脈から
if (a == static_cast(b[i]))
といったように解釈してしまう。意図が正確に反映されていないばかりか、見つけにくいバグになってしまっている。
対策
explicitキーワードをコンストラクタにつける。このキーワードがついたコンストラクタは、暗黙的な型変換には使えなくなる。
もう一つ方法はあるけど、それはトリッキーになるので略 (ヒント: proxy class (see Item 30))。
所感
暗黙的な型変換は、こちらがコーディングをミスったときに、コンパイラがプログラムをコンパイルできるように、都合良く解釈してしまうため、どこで間違いを犯したか分かりづらいことが問題。
既存の大規模プログラムに手を入れたり、バグを発見するような場合を考えると、こうした言語仕様は厄介だ。
自分の意図と異なる振る舞いをプログラムが見せることほど腹立たしいものはない。コードの字面の下で何が行われているかを、正確に把握していないと正しいコードが書けないような言語は人に優しくない。これで大規模なプロジェクトをまともに作ろうという方が無理だと思う。
自分の意図を自然に表現でき、そして書いたとおりのことがストレートにコンピュータに伝わり、実行されることが重要だろうと思う。