C/C++ - 言語仕様編 第7回 〜インスタンスとオブジェクト〜

変数の宣言と定数変数型

C++において、変数の宣言は、スコープ内のいたるところで可能となる。 あるT型変数、またはリテラル t1 があるとき、

T const t=t1;

のように、型名の後に const をつけると、tは、値としてt1を持ち、かつ、書き換えが出来ない変数となる。これをT型定数変数と呼ぶ。

ここで、tを

const T t=t1;

と初期化しても、tは、T型定数変数として機能する。このことから、tは、const T型変数であると してもよい。

C++において、定数変数は、それ自身が、リテラルとして機能することが許されるため、 たとえば、配列の要素数として、

int arr[t]={0};

などとして、t個の連続する0からなる配列を構築することが許される。

あるP型ポインタ変数、または、変数のポインタ p に対して、

P* const p1=p;
P const* p2=p;

などとすると、p1は、P*型定数ポインタ変数、p2はconst P*型ポインタ変数となる。

p1は、const T型変数がそうであったように、値としてpを持ち、かつ、そのポインタを別のポインタに変更することが 出来ないポインタ変数となる。

このとき、*p1は、P型変数であり、定数変数ではない。したがって、*p1は書き換えることが出来る。 一方、*p2は、const P型定数変数であり、書き換えが出来ない。一方、p2自身は、ポインタ変数であるため、 自由自在にそのポインタが書き換えできる。

これを組み合わせると、

P const* const p3=p;

となり、p3は、const P*型定数ポインタ変数となり、p3も*p3も書き換え不能となる。

以上のことをまとめると、C++において、ポインタ変数としての機能を持つ型には、

T*
T*const
const T*
const T*const

という4つがあると言える。

インスタンス

C言語では、動的変数の記憶領域を確保・解放するために、void *malloc(size_t)と void free(void *)関数を用いていた。 C++においては、この2つが、new、deleteという2つの演算子として改めて定義され、 この演算子により構築されたり、解体されるものを、インスタンスと呼ぶこととする。

T型インスタンスは、

new T;

として構築される。インスタンスはポインタと同等であり、あるポインタ変数pを用いて、

p=new T;

として受け取ることができる。T型インスタンスの使用が終わる場合は、動的変数と同様に、 その使用を終了するための操作が必要となり、

delete p;

として、delete演算子を用いて、pが持つインスタンスを解体する。

連続した領域に記録するための、T型配列インスタンスを構築するには、 あるポインタ変数pを用いて、

p=new T[n];

として構築する。解体する場合には、new[]演算子に対応して、

delete [] p;

とする。

オブジェクトと参照変数型

インスタンスを持つポインタ変数pならば、*p、配列インスタンスを持つポインタ変数ならば、a[i]とすることで、 使用可能となる。このような、*pとa[i]のことを、オブジェクトと呼ぶことにする。

あるオブジェクト c に対して、

T& t=c;

とすることで、tは、cがもつインスタンスをT型オブジェクトとして参照するように振る舞う。 このtをT型参照変数と呼び、また、tは、T&型オブジェクトであるとする。

オブジェクト c の型がTであるならば、それが、基本型であろうと構造体であろうと、 ポインタ変数であろうと、tとcの2つは対等となる。 すなわち、tを変更すると、それに伴って、cも同時に書き換わる。この意味で、 C++における参照変数は、C言語における参照子と同じ扱いであるとしてよい。

参照子はポインタであるため、反復子のように別のポインタを代入したりすることが、 望ましくはないが可能であった。しかし、参照変数は、一度あるオブジェクトを参照すると、 そのスコープ内においては、その参照先のオブジェクトを別のオブジェクトに変更することが一切出来ない という点で、参照子の定義をより厳密に守るものとして機能する変数である。

定数参照変数

オブジェクト c に対して、

const T& t=c;

とすると、tは、cを参照するが、cを書き換えることが出来ない参照変数となる。

参照変数に比べ、変更できなくなるというデメリットがある代わりに、 cは、なんらかの動的変数に属さないヒープ領域のポインタそのものや、 newで確保したインスタンスでない限り、リテラルであってもよいというメリットを持つ。

例えば、

int& a=3;

などということは、3がオブジェクトでないためコンパイルエラーであるが、

const int& a=3;

とすることは可能である。これは、変更しないということを前提として、 int型の一時オブジェクトを構築し、それをaが参照するということを意味する。

ちなみに、書き換えを禁止するconst T型変数をT&型変数に参照させることは出来ない。

配列参照変数

C[n]型オブジェクト配列 c を参照する配列参照変数tは、

C (&t)[n]=c;

という宣言を行う。

これと似ているものとして考えられる、参照変数の配列、すなわち、

C& t[n];

というものを作ることはできず、コンパイルエラーとなる。

ポインタ参照変数

T*型やT*const型などという、ポインタ変数としての機能を持つ型に対しても、全く同様に、 参照変数は定義される。

T*& t=t1;

T&型変数がconst T型変数を参照できないように、tとt1の型の組み合わせによっては、 コンパイルエラーとなる。

tがT*&型であるときは、T*型変数でなければ参照できない。

tがT*const&型であるときは、T*型変数とT*const型変数の2つ、 つまり、値が変更可能であるものが参照でき、一方、tがconst T*&型であるときは、 const T*型変数とconst T*const型変数の2つ、すなわち、値が変更可能でないものが参照できる。

tがconst T*const& 型であれば、4つのすべてのパターンで代入が可能となる。

ここまで見たように、参照変数に代入できる条件は、参照変数でない、 普通の変数への代入できる条件に比べると厳しい。

関数と参照変数

引数としての参照変数

参照変数は参照子と同じような機能を持つことから、当然、 もっぱら、関数の引数として使用される。

void f(C& c);

というような関数に対して、C型オブジェクトc1を、

f(c1);

として与えると、cはc1を参照するように初期化された参照変数である。

このとき、cとc1は同等となり、cを書き換えるとc1も書き換わる。これは、cが、 関数呼び出し元のc1と同じ記憶領域を共有するために起こる。

同じことは、参照子でも出来るが、参照子はポインタ変数であり、また、 ポインタ変数の初期化のために、呼び出し元で変数のポインタを渡さなくてはならないが、 参照変数を用いることで、ポインタを経由することなく同じ目的を達成できる。

参照変数がリテラルで初期化できないように、c1はリテラルであってはならない。

定数参照変数

定数であるため、関数内で呼び出し元の値を書き換えることは出来ず、 その変数に対して副作用は発生しない。

これを用いるメリットを挙げてみると、まず、その引数が呼び出し元の値を書き換えないこと、 すなわち、入力、あるいは初期化のためにのみ使われると宣言することが出来る。 もちろん、値を書き換えようとすればコンパイルエラーとなる。

続いて、参照変数が参照できない、リテラルを引数としてとることが出来る。

そして、これが最大のメリットであるが、呼び出し元と記憶領域を共有するため、 関数呼び出しに伴う、値コピーが必要とされない。

この3つの利点を持つため、非常によく多用される。 

そして、これは、返り値としてconst C&型を用いることが出来るということも表す。

参照変数を返す関数

一方、C&型変数を返す場合は、参照子の場合でそうであったように、 関数内で定義されるローカルな変数への参照を返すことは出来ない。 したがって、関数にとって、参照変数を返す場合で許されるのは、引数として呼んだ参照変数、あるいは静的変数である。

キャスト演算子

コンストラクトキャスト

オブジェクトcと、型Tに対して、T(c)とすると、これがキャスト可能であるならば、 T(c)はcで初期化されるT型オブジェクトとなる。

例えば、int型オブジェクトnに対して、double(n)とすると、 これはnの値で初期化されるdouble型オブジェクトを意味する。

これを利用すると、

T* t=new T(t1);

などとして、t1でインスタンスを初期化することが出来る。

スタティックキャスト

T型変数tから型Sへのキャストを、

static_cast<S>(t);

と書き、これをスタティックキャストと呼ぶ。T型からS型へのキャストが定義されなければ、 コンパイルの段階でエラーとなる。

コンストキャスト

const T型変数ctから型Tへのキャストを、

const_cast<T>(ct);

と書き、これをコンストキャストと呼ぶ。C言語スタイルキャストを用いない限り、 const T型をT型にキャストする方法は、これ以外に存在せず、エラーとなる。 また、constを落とす以外に、このキャストを用いてもエラーとなる。

リインタプレットキャスト

T*型変数tから型S*へのキャストを、

reinterpret_cast<S*>(t);

と書き、これをリインタプレットキャストと呼ぶ。

リインタプレットキャストは、通常の型Tに対しては用いることが出来ず、 ポインタ変数か、あるいは、T&からS&へのキャストにしか用いることは出来ない。

基本型

bool 型

C++では、true と false というbool型リテラルが定義される。 C言語における、真偽値と意味は同じであり、比較演算子が成立する場合・非零の値はtrueと等価、 そうでない場合はfalseと等価となる。

sizeof(true)とsizeof(false)、sizeof(bool)は通常1とされることが多い。

char 型

sizeof(char)は1である。char型リテラルは、'a'などと表現され、C++においてそのサイズは1である。

struct T 型

構造体struct T型は、C++において、T型と同一視される。 すなわち、typedefによる再定義なしで、struct T型をT型として使用することが出来る。 これは、共用体 union T 型に対しても同様である。

基本型とコンストラクトキャスト

すべての基本型Tに対して、コンストラクトキャスト T(const T&)が定義される。 T型へのキャストが許可される基本型Sもまたconst T&型引数として使用することが可能である。


文章作成 : yukki-ts (-+-twilight serenade-+- [stage])