メンバ変数へのポインタとは
まず、例として単に整数をラッピングしたクラスを考えてみます。
class int_wrapper {
public:
int data;
int_wrapper(int data) : data(data) {}
};
このとき、 int_wrapper::data
へのポインタ p
は
int int_wrapper::*p=&int_wrapper::data;
と書くことが出来ます。 p
に入れられるのは int_wrapper
クラス内の int
型のメンバ変数へのアドレスです。 オブジェクトではなくクラスで書いていることからわかると思いますが、 p
が指しているのはメモリ上の具体的な場所ではなく、相対的なものです。 実質的にオフセットであると考えてよいでしょう。
参照するときはどのオブジェクトの p
なのかとセットで書く必要があります。 以下のような要領です。
#include <iostream>
int main(void) {
int_wrapper foo(3);
int int_wrapper::*p=&int_wrapper::data;
std::cout << foo.*p << std::endl;
}
因みに、メンバ変数だけでなくメンバ関数でも同じような記法で扱えます。 (C++ に慣れた人であれば、メンバ変数へのポインタとメンバ関数へのポインタとは若干異なる仕組みであることは容易に想像できると思うので、ここでは触れません。)
存在しない型へのポインタ
メンバ変数へのポインタについては、対象となるクラスに該当する型のメンバが存在しなくても定義することが出来ます。 たとえば、上述の例では int_wrapper
には double
型のメンバ変数は存在しませんが、
double int_wrapper::*q;
という風に変数 q
を作っても問題ありません。 もちろん該当するメンバ変数が存在しないのですから、キャストせずに変数 q
へ入れられるのはヌルポインタだけですが。
存在しない型をどう使う?
存在しない型を指すポインタは文法の一貫性によってたまたま出来てしまったものだと思いますが、テンプレートに制約をつける用途に使えます。
以下のテンプレートは、テンプレート引数としてクラスしか渡せません。
template<class T>
class class_wrapper {
private:
typedef int T::*dummy;
public:
T data;
class_wrapper(T obj) : data(obj) {}
};
クラス T
が int
型のメンバを持っていようがいまいが int T::*
型が存在することは許されますが、 T
がクラスでなければメンバを持ち得ないのでエラーになります。
試してみましょう。
int main(void) {
int_wrapper foo(3);
class_wrapper<int_wrapper> bar(foo); // int_wrapper はクラスなので OK
class_wrapper<int> baz(5); // int はクラスではないので NG
return 0;
}
過去、現代、未来の C++
この記事で紹介した技法はもはや過去のものです。 過去において機能の足りない C++ でどのように制約を表現していたかという歴史の一端を紹介したものでしかありません。
C++11 以降では type_traits
にある is_class
を使えば上記のような制約を表現できます。 is_class
の方が何を表現しようとしているのか名前からわかりやすいのでより望ましい方法だと言えるでしょう。
テンプレートは様々な型に対して共通の機能を与える有用な機能ではありますが、必ずしもどんな型でも受け入れられるとは限りません。 想定した性質を持っていないのであればエラーにしたいという要求は当初からあり、どうやって制約を表現するのかはずっと議論されてきました。 制約を表現するための専用の機能である「コンセプト」は長く協議されながらも論争に決着が付かず、なかなか導入に至っていません。
制約の重要性
プログラムを書いたなら、それに対するテストを書くかもしれません。 正しい結果が得られるかどうかを確かめるのは大切なことです。
しかし一方では「動いて欲しくない場合」を想定するのも重要です。 特に汎用的なライブラリは作者が考えていたのとは全く違うような、思いもよらない使い方をされるかもしれません。 また、規模がある程度大きくなってくると自分自身でさえもそれほど信用できなくなってきます。
想定外の使われ方をしたときにコンパイルエラーになるというのはバグを抑止するためには有用です。