残念ながら、それらの用語は広くずさんに使われており、そうした使用法が、さもなければ理解可能であったはずの多くの叙述を台無しにしています。
話題
About: C++
この記事の目次
- 開始コンテキスト
- ターゲットコンテキスト
- オリエンテーション
- 本体
- 1: 注釈
- 2: 'データ'とは何か?
- 3: '変数'とは何か?
- 4: '表現'とは何か?
- 5: '値'とは何か?
- 6: 'データ'、'変数'、'表現'、'値'のいくつかの例
- 7: 'データタイプ'と'変数タイプ'の間の区別
- 8: ''ポインター'および'リファレンス'は変数タイプであって、データタイプではない
- 9: 結びとその先
開始コンテキスト
- 読者は、C++とはどういうものであるかの基本的な知識を持っている、たとえ、一部の、難しいと考えられている要素を正確に理解していないとしても。
ターゲットコンテキスト
- 読者は、'データ'、'変数'、'表現'、'値'がC++やその他のプログラミング言語において何であるかを、互いに明瞭に区別して理解する。
オリエンテーション
Hypothesizer 7
'データ'、'変数'、'表現'、'値'なんて十分明らかですか?...しかし、現実に、世の中では、それらの用語は、互いに明瞭に区別されて使用されておらず、それが、C++やその他のプログラミング言語についての多くのリーズナブルでない説明の原因になっている。
例えば、「C++のリファレンスとは、変数の別名のことである」といった説明をよく見かける。...本当に?...1つの例(リファレンスの典型的な使用例であって、あり得そうにない、重箱の隅をつつくような例ではない)を考えてみよう。
@C++ ソースコード
#include <iostream>
void referenceArgumentFunction (int const & a_integerReference) {
::std::cout << "### The argument value is " << a_integerReference << "." << ::std::endl << ::std::flush;
}
int main (int a_argumentsNumber, char const * a_arguments []) {
referenceArgumentFunction (2 * 3);
}
そのリファレンス('a_integerReference')が別名だという元の変数はどれなのだろうか?...'2 * 3'?'2 * 3'は、公式に変数だということになっているのか?...そうなのであれば、命題が前提としている、'変数'の定義を私は求める。
えーと、用語をもっと注意深く使うようにしませんか?...私の主要な意見の1つは、適切な記述というものは、必ず、適切な用語体系に基づくのであって、適切な用語体系においては、必ず、各用語が、単一の弁別された概念を代表していなければならないということだ。
勿論、人間の言語のほとんどの単語が実際に複数の意味を持っていることを私は知っているが、私の意見では、そうした単語は、複数の問題領域で使用されるが、各問題領域において1つの用語となり、各用語はやはり単一の意味を持っていなければならない(私が言っているのは、それが目指すべき目標であるということであって、自分が常にそれを完全に達成しているということではない)。例えば、'variable'はいくつかの意味を持つ(気象という問題領域においては、'variable'は、'貿易風'に対比される変風を意味するようだ)が、プログラミング言語という問題領域においては単一の概念を代表していなければならない。
実体とかけ離れた名称も避けるべきであると私は主張する。
例えば、私が見るたびに不平を抱く言葉の1つに、「サーバーレス」がある。全然サーバーレスでないものを「サーバーレス」と呼ばなければなりませんか?...確かに、論理的に言って、ある用語が明確に定義されて一貫性を持って使用されていれば、名称がどうであれ(いかなる名称も本質的に恣意的だ)、誤解は避けられるかもしれない。しかしながら、どの名称にも名称が暗示する意味というものがあり、その暗示に呼応するイメージを否応なしに引き起こす。「サーバーレス」という名称は、サーバーレスなもののイメージを引き起こし(私の意志に反して)、それは全然サーバーレスではないのだと、私は自らを訂正しなければならないので、それが、「サーバーレス」の使用に出会う度に私をいらだだたせるわけだ。...実のところ、そうした用語には詐欺の意図の存在を私は推測する。したがって、いかなる用語にも、その用語が代表する概念を露骨に裏切る暗示を持つ名称を与えるべきでないと、私は考える。
この記事では、4つの基本的用語、'データ'、'変数'、'表現'、'値'を弁別する。
本体
1: 注釈
Hypothesizer 7
4つの用語、'データ'、'変数'、'表現'、'値'に対する私の定義が一部の人々のものと一致しないかもしれない(例えば、一部の人々は私にとっての'データ'を意味して、'値'を使う)ことを私は承知しているし、自分の定義を誰かに押し付ける立場にない(誰でも他の用語体系を作ることができる)ことも私は承知している。
しかしながら、いかなる用語体系においても、各用語は、他から明確に区別された単一の概念を代表し、首尾一貫してその概念を意味して使用されなければならないと私は主張する。広く用いられている満足のいく用語体系が1つも見つからないから、自分で1つ作るしか選択肢がないのである(私は別に奇をてらっているわけではない)。
2: 'データ'とは何か?
Hypothesizer 7
'データ'とは何か?...データ(厳密には'メモリーデータ')とは、一片の情報の、想定される、メモリー内の単一の物理的な存在だ。
その表現の中の苦心の跡に注目してもらいたい。
第一に、ハードディスクにファイルデータも勿論あるが、この記事では私はメモリーデータについてのみ話しており、この記事では私は'メモリーデータ'を意味して'データ'を使う(毎度'メモリーデータ'を見るのは苛立たしいでしょう?)。
第二に、あるビット列が、本当にスタック内に格納されているか、CPUレジストリ内に生存しているか(もしくは、不要であるとして完全に除去されているか)に、私は全然興味がない: それは、各コンパイラの最適化の問題であって、プログラマーが煩わされなければならない問題ではないと私は考える。したがって、その保証がないことは承知しているが、いわゆる「テンポラリーオブジェクト」はどれもスタック内に格納されていると私は想定する(したがって、「テンポラリーオブジェクト」はどれもデータだ)。上の表現中の「想定される」はそういう意味だ。
第三に、データはどれも、単一の物理的な存在であり、それは、一片の情報がメモリー内に2つのコピーを持っていたら、それらコピーは2つのデータであることを意味する。
3: '変数'とは何か?
Hypothesizer 7
'変数'とは何か?...変数とは、同時には1データのみを格納できる、名前のついたボックスのことだ。
'名前のついた'という言葉を使ったことに注目してほしい。データを格納しているエリアは、どれもボックスと呼びうるが、そのボックスには名前がついていないかもしれないので、必ずしも変数ではない。
4: '表現'とは何か?
Hypothesizer 7
'表現'とは何か?...表現とは、実行時にあるデータを代表する、ソースファイル内の1文字列である。
例えば、以下はそれぞれ表現だ。
@C++ ソースコード
#include <iostream>
void function () {
int l_integer = 0;
int * l_integerPointer = &l_integer;
int & l_integerReference = l_integer;
::std::cout << "### " << l_integer << ::std::endl << ::std::flush; // "l_integer" is an expression.
::std::cout << "### " << l_integerPointer << ::std::endl << ::std::flush; // "l_integerPointer" is an expression.
::std::cout << "### " << *l_integerPointer << ::std::endl << ::std::flush; // "*l_integerPointer" is an expression.
::std::cout << "### " << l_integerReference << ::std::endl << ::std::flush; // "l_integerReference" is an expression.
::std::cout << "### " << &l_integerReference << ::std::endl << ::std::flush; // "&l_integerReference" is an expression.
::std::cout << "### " << l_integer + 1 << ::std::endl << ::std::flush; // "l_integer + 1" is an expression.
::std::cout << "### " << 2 * 3 << ::std::endl << ::std::flush; // "2 * 3" is an expression.
}
注目してほしいが、変数名はどれも、その変数に格納されたデータを代表するから、それだけで表現となれる。また、いわゆる「テンポラリーオブジェクト」を生成する文字列はどれも表現だ(「テンポラリーオブジェクト」は私の定義ではデータでありその文字列はそのデータを代表しているから)。
5: '値'とは何か?
Hypothesizer 7
'値'とは何か?...値とは、ある表現に代表されたデータのことだ。特に、ある変数に格納されたデータは、その変数の値だ。
注意してほしいが、値が値であるところの表現を特定せずに値というのはナンセンスだ: いかなる値も、ある表現の値なのだ。
したがって、いかなる値もデータであるが、どの表現にも関係づけられずに言及されるデータは、'データ'と呼ばれなければならない、'値'ではなく。
6: 'データ'、'変数'、'表現'、'値'のいくつかの例
Hypothesizer 7
以下のコードを見てみよう。
@C++ ソースコード
void function () {
int l_integer = 1 * 2 * 3;
int * l_integerPointer = &l_integer;
int & l_integerReference = l_integer;
}
'1'、'2'、'3'、'2'('1 * 2'によって生成される)、'6'('1 * 2 * 3'によって生成される)、'6'のアドレスは、実行時にメモリー内に格納されると想定され、データである。注意してほしいが、'1'というデータは、実際にはメモリー内に格納されずCPUレジスターのみに格納されるかもしれないが、それは、コンパイラが考えるべき問題(コンパイラは何をどこに置いてもよい、プログラムの約束された動作を壊さない限り)であって、プログラマーが煩わされるべき問題ではない、私の意見では。また、2つの'2'は互いに別のデータだ(ここでも、コンパイラが2つの'2'に実際に2つのエリアを割り当てるか否かに私は関心がない: それは最適化の問題だ)。
'l_integer'、'l_integerPointer'、'l_integerReference'は、変数だ。
ソースファイル内の文字列である、"1"、"2"、"3"、"1 * 2"、"1 * 2 * 3"、"&l_integer"、"l_integer"(3行目の)は、表現だ。注意してほしいが、表現はソースファイル内に存在し、その一方、データは実行時に存在する。
表現、"1"、"2"、"3"、"1 * 2"、"1 * 2 * 3"、"&l_integer"、"l_integer"の値はそれぞれ、'1'、'2'、'3'、'2'(前出の'2'とは異なるデータ)、'6'、'6'のアドレス、'6'(前出の'6'と同じデータ)というデータだ。
7: 'データタイプ'と'変数タイプ'の間の区別
Hypothesizer 7
用語が文字通り語っているとおり、'データタイプ'はデータのタイプであり、'変数タイプ'は変数のタイプであり、それら2つは別物だ。
そうあえて言ったのは、人を混乱させるようにそれらの用語を使用する記述を頻繁に見かけるからだ。
例えば、UNOタイプについてのこのドキュメントはデータタイプについてのものだと主張しているが、'any'は本当にデータタイプだろうか?...私はそう思わない。'any'は、変数タイプであって、データタイプではない: 'any'タイプのデータなどというものはなく、'any'タイプの変数(ボックス)があり、それは任意のデータを格納できる(その変数に格納されたデータは、ある時は'int'データ、またある時は'string'データであるが、決して'any'データであることはない)。
8: ''ポインター'および'リファレンス'は変数タイプであって、データタイプではない
Hypothesizer 7
通常変数(ポインターでもリファレンスでもない変数のことを私は言っている)であるか、ポインターであるか、リファレンスであるかは、変数についての話であって、データについての話ではない。
Let me see this example. 以下の例を見てみよう。
@C++ ソースコード
class ClassA {
protected:
int i_memberA;
public:
ClassA (int a_memberA) : i_memberA (a_memberA) {
}
};
class ClassB : public ClassA {
public:
ClassB (int a_memberA) : ClassA (a_memberA) {
}
};
void function () {
ClassA * l_classAPointer = new ClassB (2 * 3);
ClassA & l_classAReference = *l_classAPointer;
delete l_classAPointer;
}
「'ClassB'はリファレンスタイプか、ポインタータイプか?」といった質問がナンセンスであることは理解されるだろう: 'ClassB'はデータタイプであり、'ClassA *'はポインタータイプ、'ClassA &'はリファレンスタイプだ。
それでは、「Javaでは、クラスはリファレンスタイプである」のような言明はナンセンスではないのか?...ふーむ、それは少なくともとても妙な表現だ。ここで、もっとリーズナブルな言明を構成するよう私は試みるが、明示的にそうでないと断らない限り、C++における'ポインター'および'リファレンス'という用語を私は使用することに注意されたい: Java用語体系における「リファレンス」は、本当は、C++用語体系における'ポインター'であって、'リファレンス'ではない。
もっとリーズナブルに言うと、Javaでは、一部のクラスはデータタイプになり得る(あらゆるクラスがデータタイプになり得るわけではない、なぜなら、抽象クラスはインスタンス化できないから)が、どのクラスも変数タイプにはなり得ない、クラスインスタンスアドレスはどれも変数タイプにはなり得るが。
別の言い方をすると、Javaでは、通常クラスインスタンス変数やクラスインスタンスリファレンス変数は一切許されず、クラスインスタンスポインター変数はどれも許される。
厳密に言うと、Javaのクラスは、リファレンスタイプではなく(それがどういう意味であろうとも)、そのインスタンスに対してJavaがリファレンス(Java用語体系における)変数のみを許すデータタイプなのだ。
以下の例を見てみよう。
@Java ソースコード
class ClassA {
protected int i_memberA;
public ClassA (int a_memberA) {
i_memberA = a_memberA;
}
}
class ClassB extends ClassA {
public ClassB (int a_memberA) {
super (a_memberA);
}
}
class ClassC {
public void funtion () {
ClassA l_classAPointer = new ClassB (2 * 3);
}
}
'ClassA'というクラスはリファレンス(Java用語体系における)タイプではないのか?...そうではない。Javaのシンタックが混乱の元なのだが、"ClassA l_classAPointer"は、本当は、'ClassA * l_classAPointer'であって、'ClassA *'が本当の変数タイプだ。Javaではシンタック上、'ClassA * l_classAPointer'が、自明の'*'(なぜ自明かというと、Javaでは、そこに'*'を置くしか選択肢がないから)を省略して'ClassA l_classAPointer'と書かれるというだけのことなのだ。
9: 結びとその先
Hypothesizer 7
これで、'データ'、'変数'、'表現'、'値'が何であるかを、私は、互いに明瞭に区別して理解した。
それでは、'リファレンス'とは何なのか('オリエンテーション'において、「'C++のリファレンス'とは、変数の別名のことである」のような説明がなぜ受け入れられないかを説明した)?それについて、このシリーズの次の記事で取り組む。
'値'という用語に関連するが、「lvalue」や「rvalue」というのは何なのだろう?...うーん、それらは、実体とかけ離れた名称なのだろう(私が言っているのは、「left」や「right」の部分についてだけではなく、「value」という部分についてもだ)と私は推測している。「lvalue」や「rvalue」は本当にvalue(値)なのか?「lvalue」や「rvalue」が何であるかということを、将来の記事にて見てみよう。