一部のプログラミング言語では、const はデータが読み取り専用であることを示す型修飾子(データ型に適用されるキーワード)です。これは定数を宣言するために使用できますが、C言語ファミリーのconst は型の一部であるという点で他の言語の同様の構造と異なり、ポインタ、参照、複合データ型、および型チェックと組み合わせると複雑な動作になります。他の言語では、データは単一のメモリ位置にあるのではなく、コンパイル時に使用ごとにコピーされます。[1]これを使用する言語には、C、C++、D、JavaScript、Julia、およびRustが含まれます。
導入
オブジェクト 宣言に適用された場合、[a]はオブジェクトが定数であることを示します。つまり、変数とは異なり、その値は変更できません。この基本的な使用法 (定数を宣言する) は、他の多くの言語に類似しています。
ただし、他の言語とは異なり、C 言語ファミリーでは は型constの一部であり、オブジェクトの一部ではありません。たとえば、C では、 は型のオブジェクトを宣言します。は 型の一部であり、"(int const) x" と解析されるようなものです。一方、Adaでは、は 型の定数 (一種のオブジェクト) を宣言します。 はオブジェクトの一部ですが、型の一部ではありません。
int const x = 1;xint constconstX : constant INTEGER := 1_XINTEGERconstant
これには 2 つの微妙な結果があります。まず、 はconstより複雑な型の一部に適用できます。たとえば、 はint const * const x;定数整数への定数ポインターを宣言しますが、 はint const * x;定数整数への変数ポインターを宣言し、 はint * const x;変数整数への定数ポインターを宣言します。次に、constは型の一部であるため、型チェックの一部として一致する必要があります。たとえば、次のコードは無効です。
void f ( int & x ); // ... int const i ; f ( i );
の引数は可変f整数でなければならないのに、定数整数であるためです。この一致はプログラムの正確性の一形態であり、const 正確性として知られています。これにより、関数が引数を変更するかどうか、戻り値が変更可能かどうかなどを型シグネチャの一部として指定する、契約によるプログラミングの一形態が可能になります。この型チェックは主にポインターと参照 (整数などの基本的な値型ではなく) を対象としていますが、複合データ型やコンテナーなどのテンプレート型にも対象となります。型強制(暗黙の型変換) と C が値呼び出しであるため(C++ と D は値呼び出しまたは参照呼び出しのいずれか) 、が省略されることが多いという事実によって、これは隠されています。
iconst
結果
const 性という考えは、コンピュータのメモリに格納されている変数が書き込み不可能であることを意味するものではありません。むしろ、 const 性は、プログラマが何をすべきかを示すコンパイル時のconst構造であり、必ずしも何ができるかを示すものではありません。ただし、定義済みデータ (文字列リテラルなど)の場合、C は書き込み不可能であることが多いことに注意してください。
char const * const
定数との区別
定数はプログラムの実行中に値を変更しませんが、宣言されたオブジェクトはconstプログラムの実行中に値を変更する可能性があります。一般的な例としては、デジタル入力の現在の状態など、組み込みシステム内の読み取り専用レジスタがあります。デジタル入力のデータ レジスタは、多くの場合、 および として宣言されますconst。volatileこれらのレジスタの内容は、プログラムが何もしなくても変更される可能性があります ( volatile) が、プログラムがそれらに書き込もうとするのは不正な形式です ( const)。
その他の用途
さらに、(非静的)メンバー関数は として宣言できますconst。この場合、そのような関数内のthisポインタはobject_type const *、単に 型ではなく型になりますobject_type *。[2]これは、このオブジェクトの非 const 関数をそのような関数内から呼び出すことはできず、メンバー変数を変更することもできないことを意味します。C++ では、メンバー変数を として宣言して、この制限が適用されないことを示すことができます。場合によっては、これは、キャッシュ、参照カウント、およびデータ同期mutableなどに役立ちます。これらの場合、オブジェクトの論理的な意味(状態)は変更されませんが、ビット単位の表現が変わる可能性があるため、オブジェクトは物理的に一定ではありません。
構文
C、C++、Dでは、ユーザーが定義したものも含め、すべてのデータ型を宣言することができconst、constの正確性により、変更する必要がない限り、すべての変数またはオブジェクトをそのように宣言する必要があります。 を積極的に使用することで、const値が「理解しやすく、追跡しやすく、推論しやすくなる」ため、[3]コードの可読性とわかりやすさが向上し、値の意図された使用に関する情報が伝達されるため、チームでの作業やコードの保守が簡単になります。これは、コードについて推論する際にコンパイラだけでなく開発者にも役立ちます。また、最適化コンパイラがより効率的なコードを生成できるようにすることもできます。[4]
シンプルなデータ型
単純な非ポインタ データ型の場合、const修飾子の適用は簡単です。歴史的な理由により、一部の型では修飾子をどちらの側にも配置できます (たとえば、は とconst char foo = 'a';同等です)。実装によっては、 2 回char const foo = 'a';使用すると(たとえば、または)、警告は生成されますが、エラーは生成されません。
constconst char constchar const const
ポインタと参照
ポインタ型と参照型の場合、 の意味はconstより複雑です。ポインタ自体、または指し示されている値、あるいはその両方が になることがあります。さらに、構文がわかりにくい場合があります。ポインタは、書き込み可能な値へのポインタ、値への書き込み可能なポインタ、または値へのポインタconstとして宣言できます。constconstconstconstポインターconstは、最初に割り当てられたオブジェクトとは別のオブジェクトを指すように再割り当てすることはできませんが、ポインターが指す値 (ポインターと呼ばれる) を変更するために使用できます。[5] [6] [7] [8] [9] C++ の参照変数は、ポインターの代替構文です。一方、オブジェクトconstへのポインターは、別のメモリ位置 (同じ型または変換可能な型のオブジェクトである必要があります) を指すように再割り当てできますが、ポインターが指しているメモリを変更することはできません。オブジェクトへのポインターは宣言することもできますが、ポインターを変更するために使用することも、別のオブジェクトを指すように再割り当てすることもできません。次のコードは、これらの微妙な違いを示しています。
constconstconst
void Foo ( int * ptr , int const * ptrToConst , int * const constPtr , int const * const constPtrToConst ) { * ptr = 0 ; // OK: 指し示されたデータを変更しますptr = NULL ; // OK: ポインタを変更します
* ptrToConst = 0 ; // エラー! 指し示されたデータを変更できませんptrToConst = NULL ; // OK: ポインタを変更します
* constPtr = 0 ; // OK: 指し示されたデータを変更しますconstPtr = NULL ; // エラー! ポインタを変更できません
* constPtrToConst = 0 ; // エラー! 指し示されたデータを変更できませんconstPtrToConst = NULL ; // エラー! ポインタを変更できません}
C規約
通常の C の宣言規則に従い、宣言は使用の後に続き、*ポインター内の はポインター上に記述され、の逆参照を示します。たとえば、宣言 ではint *ptr、逆参照された形式*ptrは ですintが、参照形式ptrは へのポインターですint。したがって、 は右側の名前constを変更します。C++ 規則では、代わりに のように を型に関連付け、 は左側の型を変更するものとして読み取ります。したがって、 は「は」(値は定数)または「は」(ポインターは定数整数へのポインター)と読み取ることができます。つまり、
*int* ptrconstint const * ptrToConst*ptrToConstint constptrToConstint const *
int * ptr ; // *ptr は int 値int const * ptrToConst ; // *ptrToConst は定数 (int: 整数値) int * const constPtr ; // constPtr は定数 (int *: 整数ポインタ) int const * const constPtrToConst ; // constPtrToConst は定数ポインタであり、// 定数値を指します
C++ 規約
値ではなく型を分析するという C++ の慣例に従い、宣言を右から左に読むのが原則int const *です。したがって、星の左側にあるものはすべて、指し示す型として識別でき、星の右側にあるものはすべてポインター プロパティです。たとえば、上記の例では、は書き込み不可能な整数を参照する書き込み可能なポインターとして読み取ることができ、int * const書き込み可能な整数を参照する書き込み不可能なポインターとして読み取ることができます。
複雑な宣言と定義を理解するのに役立つ、より一般的なルールは次のようになります。
- 宣言を理解したい識別子を見つける
- 可能な限り右に読みます(つまり、宣言の終わりまで、または次の閉じ括弧まで、どちらか先に来るまで)。
- 開始した場所に戻り、左に向かって逆方向に読みます(つまり、宣言の先頭まで、または前の手順で見つかった閉じ括弧と一致する開き括弧まで)。
- 宣言の先頭に到達したら完了です。そうでない場合は、最後に一致した閉じ括弧を超えて、手順 2 に進みます。
次に例を示します。
左に読む場合、要素を右から左に読むことが重要です。したがって、はconst int へのポインターint const *になり、int への const ポインターにはなりません。
constC/C++ では、キーワードを型の左側に配置できる場合
もあります。次に例をいくつか示します。
const int * ptrToConst ; // 次と同じ: int const *ptrToConst、const int * const constPtrToConst ; // 次と同じ: int const *const constPtrToConst
C/C++ ではこのような定義 (定義を左から右に読むと英語とほぼ一致する) が許可されていますが、コンパイラは前述の手順に従って定義を右から左に読み取ります。ただし、定数である必要があるものをconst 前に置くと、意図した内容とコンパイラが記述したと判断する内容の間にすぐに不一致が生じます。ポインターへのポインターについて考えてみましょう。
int ** ptr ; // int へのポインタへのポインタint const ** ptr // 定数 int 値へのポインタへのポインタ// (int への定数ポインタへのポインタではありません) int * const * ptr // int 値への const ポインタへのポインタ// (int へのポインタへの定数ポインタではありません) int ** const ptr // int へのポインタへの定数ポインタ// (ptr は識別子であり、const であることに意味はありません) int const ** const ptr // 定数 int 値へのポインタへの定数ポインタ
ポインター定義に関する最後の注意点として、ポインター シンボル (*) は常に可能な限り右側に記述してください。ポインター シンボルを型に付加するのは注意が必要です。これは、ポインター型であることを強く示唆しますが、実際にはそうではないからです。次に例をいくつか示します。
int * a ; /* 書き込み: */ int * a ; // a は int へのポインターですint * a , b ; // 紛らわしい/* 書き込み: */ int * a , b ; // a は int へのポインターですが、 // b は単なる int ですint * a , * b ; // 醜い: a と b は両方とも int へのポインターです/* 書き込み: */ int * a , * b ;
Bjarne StroustrupのFAQでは、この問題を回避するために、C++規則を使用する場合は1行に1つの変数のみを宣言することを推奨しています。[10]
参照と右辺値参照の定義にも同じ考慮事項が適用されます。
int var = 22 ; int const & refToConst = var ; // OK int const & ref2 = var , ref3 = var ; // 混乱を招く: // ref2 は参照ですが、ref3 はそうではありません: // ref3 は、 var の値で初期化された定数 int ですint & const constRef = var ; // エラー: 参照はいずれにしても変更できないため。
// C++:
int && rref = int ( 5 ), value = 10 ; // 紛らわしい: // rref は右辺値参照ですが、value は// 単なる int です。/* 書き込み: */ int && rref = int ( 5 ), value = 10 ;
多次元配列やポインタへの参照 (またはポインタ) を使用する場合、より複雑な宣言が発生します。このような宣言は混乱を招きやすく、エラーが発生しやすいため、使用を避けるか、より高レベルの構造に置き換える必要があると主張されることもありますが(誰が主張したのでしょうか)、このセクションの冒頭で説明した手順は、あいまいさや混乱を招くことなく常に使用できます。
パラメータと変数
constは、関数パラメータと変数(staticまたは automatic、グローバルまたはローカルを含む)の両方で宣言できます。解釈は使用方法によって異なります。staticconst変数(グローバル変数または static ローカル変数)は定数であり、 などの数学定数などのデータに使用できます。double const PI = 3.14159現実的には長いです。または、コンパイル時の全体的なパラメータです。automaticconst変数(非 static ローカル変数)は、などのように、毎回異なる値が使用される場合もありますが、単一の割り当てint const x_squared = x * xが行われていることを意味します。pass-by-reference のパラメータconstは、参照された値が変更されないことを意味します。これは契約の一部です。一方、constpass-by-value のパラメータ(または pass-by-reference のポインタ自体)は、インターフェイスに何も追加しませんが(値がコピーされているため)、内部的には関数がパラメータのローカル コピーを変更しないことを示します(単一の割り当てです)。このため、一部の人は、constコントラクトを変更する pass-by-reference にのみパラメータで を使用し、実装を公開する pass-by-value には使用しないことを好みます。
C++
方法
メソッドとメンバー データを持つことができるユーザー定義型 (構造体とクラス) の契約による設計アプローチを利用するために、プログラマは、constオブジェクトのデータ メンバーを変更しないものとしてインスタンス メソッドにタグを付けることができます。constこのように、インスタンス メソッドに修飾子を適用することは、const の正確性にとって不可欠な機能であり、JavaやC#などの他の多くのオブジェクト指向言語や、MicrosoftのC++/CLIまたはManaged Extensions for C++では利用できません。メソッドはオブジェクトと非オブジェクトの両方から同様に呼び出すことができますが、非メソッドは非オブジェクトによってのみ呼び出すことができます。インスタンス メソッドの修飾子は、すべてのインスタンス メソッドに渡される暗黙の引数である " " ポインターによってポイントされるオブジェクトに適用されます。したがって、メソッドを持つことは、他の引数と同様に
暗黙の " " ポインター引数に const の正確性を適用する方法です。constconstconstconstconstconstthisconstthis
この例は以下を示しています。
class C { int i ; public : int Get () const // 「const」タグに注意してください{ return i ; } void Set ( int j ) // 「const」がないことにご注意ください{ i = j ; } };
void Foo ( C & nonConstC , C const & constC ) { int y = nonConstC . Get (); // Ok int x = constC . Get (); // Ok: Get() は const です
nonConstC . Set ( 10 ); // OK: nonConstC は変更可能ですconstC . Set ( 10 ); // エラー! Set() は非 const メソッドであり、constC は const 修飾オブジェクトです}
上記のコードでは、暗黙的な " this" ポインターのSet()型は " C *const" ですが、 " this" ポインターのGet()型は " C const *const" であり、メソッドが " " ポインターを介してオブジェクトを変更できないことを示していますthis。
多くの場合、プログラマーは、両方のタイプの呼び出し元に対応するために、クラス内で同じ名前 (ただし、用途がまったく異なる可能性もある) の メソッドconstと非 メソッドの両方を提供しますconst。次の点を考慮してください。
クラスMyArray { int data [ 100 ]; public : int & Get ( int i ) { return data [ i ]; } int const & Get ( int i ) const { return data [ i ]; } };
void Foo ( MyArray & array , MyArray const & constArray ) { // 配列要素への参照を取得し、その参照値を変更します。
array.Get ( 5 ) = 42 ; // OK! (呼び出し: int & MyArray::Get(int)) constArray.Get ( 5 ) = 42 ; //エラー! (呼び出し: int const & MyArray:: Get (int) const) }
const呼び出し元オブジェクトの であるかどうかによって、呼び出される のバージョンが決まり、MyArray::Get()呼び出し元に参照が与えられ、その参照を使用してオブジェクト内のプライベート データを操作できるか、それとも単に観察できるかが決まります。 2 つのメソッドは、その " this" ポインターの型が異なるため、技術的にはシグネチャが異なり、コンパイラーが適切な型を選択できます。 ( を単に値で返すのではなく、constへの参照を返すことは、2 番目のメソッドではやり過ぎかもしれませんが、標準テンプレート ライブラリのように、任意の型に同じ手法を使用できます。)
intint
定数の正確さの抜け穴
C および C++ には、純粋な const の正確性に対する抜け穴がいくつかあります。これらは主に、既存のコードとの互換性のために存在します。
1 つ目は、C++ にのみ適用される の使用です。const_castこれにより、プログラマーはconst修飾子を削除して、任意のオブジェクトを変更可能にすることができます。修飾子を削除する必要性は、変更できないが const が正しくない既存のコードやライブラリを使用する場合に生じます。たとえば、次のコードを考えてみましょう。
// 変更できないが、渡されたポイント先を変更しないことがわかっている
関数のプロトタイプ。 void LibraryFunc ( int * ptr , int size );
void CallLibraryFunc ( int const * ptr , int size ) { LibraryFunc ( ptr , size ); // エラー! const 修飾子を削除します
int * nonConstPtr = const_cast < int *> ( ptr ); // 修飾子を削除しますLibraryFunc ( nonConstPtr , size ); // OK }
ただし、 const キャストconstによって宣言されたオブジェクトを変更しようとすると、ISO C++ 標準に従って未定義の動作が発生します。上記の例では、 がとして宣言されたグローバル変数、ローカル変数、またはメンバー変数、または を介してヒープ上に割り当てられたオブジェクトを参照する場合、が によって指される値を変更しない場合にのみ、コードは正しいものになります。
ptrconstnew int constLibraryFuncptr
C 言語には、特定の状況が存在するため抜け穴が必要です。静的記憶期間を持つ変数は、初期値で定義できます。ただし、初期化子は文字列定数やその他のリテラルなどの定数のみを使用でき、初期化子要素が宣言されているかどうかconst、または静的期間変数が宣言されているかどうかに関係なく、変数名などの非定数要素を使用することはできませんconst。静的記憶期間を持つ変数を初期化する非移植的な方法がありますconst。後の割り当ての左側で型キャストを慎重に構築することで、const変数に書き込むことができ、属性を効果的に取り除き、他の変数などのconst非定数要素で「初期化」できます。この方法で変数に書き込むと、意図したとおりに機能するかもしれませんが、未定義の動作が発生し、const の正確性に大きく反します。
constconst
size_t const bufferSize = 8 * 1024 ; size_t const userTextBufferSize ; //初期値はconst bufferSizeに依存するため、ここでは初期化できません
...
int setupUserTextBox ( textBox_t * defaultTextBoxType , rect_t * defaultTextBoxLocation ) { * ( size_t * ) & userTextBufferSize = bufferSize - sizeof ( struct textBoxControls ); // 警告: 動作するかもしれませんが、C では保証されません... }
もう 1 つの抜け穴[11]は、C と C++ の両方に当てはまります。具体的には、これらの言語では、メンバー ポインタと参照は、その所有者の -ness に関して「浅い」ものであると規定されています。constつまり、メンバーのポインタ先 (および参照先) を除くconstすべてのconstメンバーを含むオブジェクトは、依然として変更可能です。説明のために、次の C++ コードを考えてみましょう。
構造体S { int val ; int * ptr ; };
void Foo ( S const & s ) { int i = 42 ; s . val = i ; // エラー: s は const なので、val は const int s . ptr = & i ; // エラー: s は const なので、ptr は int *への const ポインターですs . ptr = i ; // OK: ptr が指すデータは常に変更可能ですが、// これが望ましくない場合もあります}
s渡されるオブジェクトはFoo()定数であり、そのメンバーはすべて定数になりますが、 を介してアクセス可能なポイント先s.ptrは依然として変更可能です。ただし、がポイント先を独占的に所有する可能性があるconstため、これは -正確性の観点からは望ましくない可能性がsあります。このため、Meyers は、メンバー ポインターと参照のデフォルトは「深い」const-ness であるべきだと主張しています。これは、ポイント先がコンテナーによって所有されていない場合に修飾子によってオーバーライドできますmutableが、この戦略では既存のコードとの互換性の問題が発生します。したがって、歴史的な理由により[引用が必要]、この抜け穴は C と C++ で開いたままになっています。
後者の抜け穴は、クラスを使用してポインターをconst正しいインターフェースの背後に隠すことで閉じることができますが、そのようなクラスは、オブジェクトからの通常のコピー セマンティクスをサポートしていないかconst(つまり、通常のセマンティクスでは包含クラスをコピーできない)、または不注意または意図的なコピーによって -ness が削除されることを許可することで他の抜け穴を許しますconst。
最後に、C 標準ライブラリのいくつかの関数は、文字列へのポインターを受け入れて、同じ文字列の一部への非ポインターを返すため、 C23より前の const の正確性に違反しています。および はこれらの関数の 1 つです。Microsoft の[12]などの C++ 標準ライブラリの実装では、一部の関数の 2 つのオーバーロードバージョン (" " バージョンと " 以外のバージョン)を提供することで、この抜け穴を塞ごうとしています。
constconststrstrstrchrconstconst
問題
型システムを使用して不変性を表現すると、さまざまな複雑さや問題が生じるため、批判され、C、C++、D の狭い C ファミリー以外では採用されていません。C と C++ の影響を強く受ける Java と C# は、どちらもconstスタイルの型修飾子を明示的に拒否し、代わりに識別子に適用されるキーワード ( finalJava では 、constC readonly# では ) によって不変性を表現しています。C と C++ 内でも の使用法はconst大きく異なり、一貫して を使用するプロジェクトや組織もあれば、 を避けるプロジェクトや組織もあります。
strchr問題
型const修飾子は、関数のロジックが入力が定数かどうかに関わらず、入力と同じ修飾型の値を返す場合に問題を引き起こします。言い換えると、これらの関数では、入力が定数 (const 修飾) の場合は戻り値も同様にする必要がありますが、入力が変数 (修飾されていないconst) の場合は戻り値も同様にする必要があります。これらの関数の型シグネチャは異なるため、同じロジックを持つ 2 つの関数 (複数の入力の場合はさらに多く必要になる場合があります) が必要になります。これは、ジェネリック プログラミングの一形式です。
この問題は、C標準ライブラリの単純な関数、特に の場合でも発生しますstrchr。この観察は、1980年代半ばにリッチーがトム・プラムに与えたものです。[13]関数は文字列内の文字を検索します。正式には、文字列 内でstrchr文字が最初に出現する位置へのポインターを返します。古典的なC(K&R C)では、そのプロトタイプは次のようになります。
cs
char * strchr ( char * s , int c );
このstrchr関数は入力文字列を変更しませんが、戻り値は多くの場合、呼び出し元によって文字列を変更するために使用されます。次に例を示します。
p = strchr ( q , '/' )の場合* p = ' ' ;
したがって、一方では、入力文字列はになる可能性がありconst(関数によって変更されていないため)、入力文字列が の場合はconst戻り値も同様になるはずです (最も単純な理由は、最初の文字が一致した場合、入力ポインターを正確に返す可能性があるため)。しかし他方では、const元の文字列が でなかった場合const、呼び出し元がポインターを使用して元の文字列を変更する可能性があるため、戻り値は になるべきではありません。
C++では、これは関数オーバーロードによって行われ、通常はテンプレートconstを介して実装され、2つの関数が生成され、戻り値は入力と同じ -修飾型になります。 [b]
char * strchr ( char * s , int c ); char const * strchr ( char const * s , int c );
これらはテンプレートによって定義できます。
テンプレート< T > T * strchr ( T * s , int c ) { ... }
Dでは、これはキーワードによって処理されますinout。これはconst、不変、または非修飾(変数)のワイルドカードとして機能し、次のようになります。[14] [c]
inout ( char )* strchr ( inout ( char )* s , int c );
ただし、C では関数のオーバーロードがないため、どちらも不可能であり、代わりに、入力は定数だが出力は書き込み可能な単一の関数によって処理されます。
char * strchr ( char const * s , int c );
これにより、慣用的な C コードが可能になりますが、入力が実際に const 修飾子付きである場合は const 修飾子が削除され、型の安全性が侵害されます。この解決策は Ritchie によって提案され、その後採用されました。この違いは、C と C++ の互換性の失敗の 1 つです。
C23以降、この問題はジェネリック関数の使用によって解決されています。strchrこの問題の影響を受ける他の関数は、constポインタが渡された場合はポインタを返し、非修飾ポインタが渡された場合は非修飾ポインタを返します。[15]
だ
Dプログラミング言語のバージョン2には、constに関連する2つのキーワードが存在します。[16]キーワードimmutableは、いかなる参照によっても変更できないデータを表します。 キーワードconstは、可変データの非可変ビューを表します。C++とは異なりconst、Dconstと はimmutable「深い」または推移的であり、 または オブジェクトを介して到達可能なものはconstそれぞれまたはimmutableです。
constimmutable
D における const と immutable の例
int [] foo = new int [ 5 ]; // foo は可変です。const int [] bar = foo ; // bar は可変データの const ビューです。immutable int [] baz = foo ; // エラー: 不変データのすべてのビューは不変である必要があります。
immutable int [] nums = new immutable ( int )[ 5 ]; // nums への可変参照は作成できません。const int [] constNums = nums ; // 動作します。immutable は暗黙的に const に変換できます。int [] mutableNums = nums ; // エラー: 不変データの可変ビューを作成できません。
D における推移的または深い const の例
クラスFoo { Foo next ; int num ; }
不変Foo foo = new immutable ( Foo ); foo . next . num = 5 ; // コンパイルされません。foo.next は immutable(Foo) 型です。// foo.next.num は immutable(int) 型です。
歴史
constは、1981年にC++の前身であるC with ClassesでBjarne Stroustrupによって導入され、当初はと呼ばれていました。[17] [18]動機について、Stroustrupは次のように書いています。[18]readonly
- 「それは 2 つの機能を果たしました。スコープと型のルールに従うシンボリック定数を定義する方法として (つまり、マクロを使用せずに)、そしてメモリ内のオブジェクトを不変と見なす方法としてです。」
最初の用途は、マクロのスコープと型が指定された代替手段として、inlineキーワードを介して関数のようなマクロに対して同様に実現されました。定数ポインタとその* const表記法は、デニス・リッチーによって提案され、採用されました。[18]
constその後、C言語では標準化の一環として採用され、他の型修飾子 とともにC89volatile (およびそれ以降のバージョン)に登場しました。[19] 1987年12月のX3J11委員会の会議では、さらに別の修飾子noaliasが提案されましたが、却下されました。その目的は最終的にC99restrictの キーワードによって達成されました。リッチーはこれらの追加をあまり支持せず、それらは「役割を果たさない」と主張しましたが、最終的には標準から削除することを主張しませんでした。[20]
D はその後 C++ から継承され、そこでは型コンストラクタ(型修飾子constではない)として知られ、関連するユースケースを処理するためにさらに 2 つの型コンストラクタとが追加されました。 [d]immutableinout
その他の言語
他の言語では、C/C++ のように型の定数部分を持ちませんが、表面的には同様の構造を持ち、constキーワードを使用することもあります。通常、これは定数 (定数オブジェクト) にのみ使用されます。
C# にはconstキーワードがありますが、その意味は根本的に異なり、より単純です。つまり、これはコンパイル時の定数を意味し、型の一部ではありません。
Nim には C# と同様のキーワードがありconst、型の一部を形成するのではなく、コンパイル時の定数を宣言します。ただし、Nim では、コンパイル時に評価できる任意の式から定数を宣言できます。[21] C# では、C# 組み込み型のみを として宣言できますconst。クラス、構造体、配列などのユーザー定義型は として宣言できませんconst。[22]
Java には がありませんconst。代わりに がありfinal、これはローカルの「変数」宣言に適用でき、型ではなく識別子に適用されます。Java では、オブジェクト メンバーに対して異なるオブジェクト指向の使用方法があり、これが という名前に由来しています。
Java言語仕様ではconst、予約語(変数識別子として使用できないもの)とみなされていますが、意味は割り当てられていません。予約語(識別子で使用できない)ですが、キーワードではありません(特別な意味はありません)。このキーワードは、JavaコンパイラがC++キーワードの誤った使用を検出して警告するための手段として含まれていました。[23]正確性を実装するための機能強化リクエストチケットがJavaコミュニティプロセスconstに存在しますが、下位互換性のある方法で実装することは不可能であるという理由で2005年にクローズされました。[24]
同時代のAda 83には、定数オブジェクトとキーワードの概念が独立して存在しconstant、[25] [e]入力パラメータとループパラメータは暗黙的に定数である。ここで、はconstantオブジェクトのプロパティであり、型のプロパティではない。
JavaScriptには、再割り当てや再宣言ができないブロックスコープconst変数を定義する宣言があります。これは、再定義できない変数への読み取り専用参照を定義しますが、変数がオブジェクトを参照し、そのプロパティが変更された場合など、状況によっては変数の値自体が変化する可能性があります。[26]
参照
注記
- ^ 正式には、宣言内の最も外側の派生型の一部である場合、ポインターは議論を複雑にします。
const - ^ ポインタ宣言の構文規則は C と C++ では異なることに注意してください。C では が
char *s標準ですが、C++ では がchar* s標準です。 - ^ 慣用的なDコードでは、ここではポインタの代わりに配列を使用します。[14]
- ^ D では
shared型コンストラクタも導入されましたが、これは の使用例に関連しvolatile、 の使用例には関連しませんconst。 - ^ Ada 標準ではこれを「予約語」と呼んでいます。使用方法についてはその記事を参照してください。
参考文献
- ^ 「定数項目 – Rust リファレンス」。doc.rust-lang.org 。2022年 6 月 22 日閲覧。
- ^ 「this ポインター」。ドラフト C++ 標準。2020 年 3 月 30 日閲覧。型が
cv-qualifier-seq cv を
持ち、クラスがである
メンバー関数の の
型は、「
cv
へのポインター
」です。
thisXX - ^ Herb SutterおよびAndrei Alexandrescu ( 2005)。C ++ コーディング標準。p. 30。ボストン: Addison Wesley。ISBN 0-321-11358-6
- ^ 「なぜ kfree() 引数は const なのでしょうか?」lkml.org. 2013-01-12.
- ^ 「5.1. GNU Fortran で実装された拡張機能: 5.1.16 Cray ポインター」。GNU Fortran コンパイラ。2006 年。2022 年 12 月 21 日時点のオリジナルよりアーカイブ。2022年 12 月 21 日閲覧。
- ^ Fahey, Mark R.; Nagle, Dan (1999-04-19). 「Cray Fortran ポインターと Fortran 90 ポインターの比較、および Cray C90 から SGI Origin2000 への移植」(PDF)。マサチューセッツ州ビックスバーグ、米国: 米国陸軍工兵隊水路実験ステーション、主要共有リソース センター。2022年 12 月 23 日のオリジナルからアーカイブ(PDF) 。2022年 12 月 23 日閲覧。(8ページ)
- ^ 「付録 C: Fortran 90 の機能と相違点 > 機能 > Cray ポインター」。Fortranユーザーズ ガイド。Oracle Corporation。2010年。2021 年 9 月 21 日時点のオリジナルよりアーカイブ。2022年 12 月 23 日閲覧。
- ^ 「付録 C: Fortran 90 の機能と相違点 > 機能 > Cray 文字ポインター」。Fortranユーザーズ ガイド。Oracle Corporation。2010年。2022 年 12 月 23 日時点のオリジナルよりアーカイブ。2022年 12 月 23 日閲覧。
- ^ 「第4章 データ型」。Fortran言語リファレンスマニュアル、第1巻。第1巻。Silicon Graphics、Inc. 1999 [1993]。文書番号:007-3692-004。2022年12月23日時点のオリジナルよりアーカイブ。 2022年12月23日閲覧。(注: Walter S. Brainerd、Jeanne C. Adams、Jeanne T. Martin、Brian T. Smith、および Jerrold L. Wagener 著「FORTRAN 90 HANDBOOK」(1992、McGraw-Hill, Inc. ) から派生。)
- ^ 「Stroustrup: C++ スタイルとテクニックに関する FAQ」。
- ^ スコット・マイヤーズ(2005)。Effective C++、第 3 版。pp . 21–23。ボストン: Addison Wesley。ISBN 978-0-321-33487-9
- ^ "strchr、wcschr、_mbschr (CRT)". Msdn.microsoft.com . 2017 年 11 月 23 日閲覧。
- ^ 「Dennis Ritchie: X3J11 型修飾子が気に入らない理由」
- ^ ab Dプログラミング言語、 Andrei Alexandrescu、8.8: パラメータから結果への修飾子の伝播
- ^ 「WG14-N3020 : 修飾子を保持する標準ライブラリ関数」(PDF) . open-std.org . 2022-06-13. 2022-10-13時点のオリジナルよりアーカイブ(PDF) 。
- ^ 「const(FAQ) – Dプログラミング言語」 Digitalmars.com . 2013年8月18日閲覧。
- ^ Bjarne Stroustrup、「C言語型概念の拡張」、ベル研究所内部技術メモ、1981年1月5日。
- ^ abc 兄弟対決: C と C++、Bjarne Stroustrup、2002 年、5 ページ
- ^ Dennis M. Ritchie、「C 言語の開発」、2012 年 7 月 15 日にarchive.todayにアーカイブ、2003 年:「X3J11 では、型修飾子constやvolatile、わずかに異なる型昇格ルールなど、多数の小さな追加機能や調整も導入されました。」
- ^ 「まず、12 月以前の修飾子 ('const' と 'volatile') でさえ、その価値が十分にあるとは思えないことを述べさせてください。これらの修飾子によって言語の学習と使用にかかるコストが、表現力の向上に結びついていないのではないかと思います。特に 'volatile' は難解なアプリケーションのための飾りで、他の手段で表現した方がはるかに優れています。その主な利点は、ほとんどすべての人がそれを忘れることができることです。'const' はより便利であると同時により目障りです。ライブラリ インターフェイスに存在するため、それについて学ぶことは避けられません。とはいえ、私は修飾子の根絶を主張しません。それは、単に遅すぎるという理由だけでです。」
- ^ Nim マニュアル: Const セクション
- ^ const (C# リファレンス)
- ^ Gosling, James; Joy, Bill; Steele, Guy. 「Java 言語仕様第 3 版」。
- ^ 「バグID: JDK-4211070 Javaはコードのメンテナンスのためにconstパラメータ(C++など)をサポートすべき」[sic]。Bugs.sun.com 。2014年11月4日閲覧。
- ^ 1815A [リンク切れ ]、3.2.1. オブジェクト宣言 Archived October 20, 2014, at the Wayback Machine :
「予約語 constant がオブジェクト宣言に出現する場合、宣言されたオブジェクトは定数です。その場合、宣言には明示的な初期化を含める必要があります。定数の値は初期化後に変更できません。サブプログラムおよびエントリの mode in の形式パラメータ、および mode in の汎用形式パラメータも定数です。ループパラメータは対応するループ内の定数です。定数のサブコンポーネントまたはスライスは定数です。」 - ^ "const". MDN . 2017年10月31日閲覧。
外部リンク
- ハーブ・サッター著「Const-Correctness」
- 「コンスタントな最適化?」ハーブ・サッター著
- C++ FAQ Lite: Const の正確性 (Marshall Cline 著)
- Bruce Eckel著の無料電子書籍「Thinking in C++」のセクション「値の置換」
- 「ここに定数、そこに定数」ウォルター・ブライト
- D プログラミング言語仕様バージョン 2 (実験的) の「Const と Invariant」
