12.型
C++/CLI の全ての値は型を持っています。型は以下のテーブルに記述される7つのカテゴリーにグループ分けされます。
| Type Category | Type Subcategory |
| ネイティブ・クラス |
POD 共用型(union) |
| Ref(委託)クラス |
ボックス化値型 デリゲート CLI配列 |
| 値型 |
基本型 列挙型 ポインタ 値クラス |
| インターフェイス |
|
| ネイティブ配列 |
|
| ハンドル |
|
| 参照 |
ネイティブ参照 追跡参照 |
refクラス型、値クラス型、そして、インターフェイス型はまとめて
CLIクラス型として知られています。
標準 C++ (§3.9/10 )の
スカラー型の定義は以下のように拡張されています。
算術型(3.9.1 )、列挙型、ハンドル型、ポインタ型、メンバ型へのポインタ(3.9.2 )、そして、それらの cv-qualified (const ないし volatile 修飾された)版(3.9.3 )はまとめてスカラー型と呼ばれます。
標準C++ (§7.1.5 )の
type-specifier(型指定子)の定義は以下のように拡張されました。
type-specifier:
simple-type-specifier
class-specifier
enum-specifier
elaborated-type-specifier
cv-qualifier
delegate-specifier
型 long long int と unsigned long long int を協調して追加するために、標準 C++(§.1.5.2/表 7 )には次の行が追加されます。
| 指定子 | 型 |
| long long | "signed long long int" |
| signed long long | "signed long long int" |
| long long int | "signed long long int" |
| signed long long int | "signed long long int" |
| unsigned long long | "unsigned long long int" |
| unsigned long long int | "unsigned long long int" |
12.1 値型
値型は基本型、列挙、ポインタ、そして、値クラスから成り立っています。[注記:標準 C++ ではクラス型と非クラス型との間に区別があります。C++/CLI では基本型と列挙は共に双方の文字表記を持っています。(
§12.1.1 参照)ポインタを除いた全ての値型は、ボックス化変換(
§14.2.6 )を通してボックス化される能力を持っています。]
基本型は言語による "built-into(ビルトイン)"であり、それに関連したキーワードを持っています。
列挙型は enum, enum class、ないし、enum struct キーワードよって宣言されます。ポインタは宣言子にアスタリスクを付けて宣言されます。値クラスは value class、ないし、value struct キーワードで宣言されます。
12.1.1 基本型
型 long long int と型 unsigned long long int と拡張整数型を協調的に追加するために、標準 C++(§9.1 )は以下のように拡大されます。
-
§.9.1/2 : "
4つの5つの標準 signed integer type(符号付き整数型): "signed char", "short int", "int", そして、 "long int"、そして、"long long int" があります。このリストには、それぞれの型は少なくともリスト中に前述したのと同じだけのストレージを提供します。素の int は実行環境のアーキテクチャにとって指示される自然な大きさを持ちます。そのほかの符号付き整数値は特殊な必要において提供されます。実装定義の extended signed integer type (拡張符号付き整数型)も存在します。標準と拡張符号付き整数型は総称として signed integer type (符号付き整数型)と呼ばれます。"
- §.9.1/3 : "標準符号付き整数型のそれぞれに、対応した(しかし、別物な)標準 unsigned integer type(符号無し整数値) : "unsinged char", "unsigned short int", "unsigned int",
そして、"unsigned long int", そして、"unsigned long long int" が存在しており、それらはそれぞれが対応する符号付き整数型と同じアライメント要請(3.9)を持ち、同じ量のストレージを占めています。同様に拡張符号付き整数型のそれぞれごとに同じ量のストレージとアライメント要請の拡張符号無し整数型が存在します。標準、拡張符号無し整数型は総称して符号無し整数型と呼ばれます。符号付き整数型の非負値の範囲は対応する符号無し整数型に含まれる範囲であり、それぞれに対応する符号付き/符号無し型の値表現は同じであるべきです。標準符号付き整数型と標準符号無し整数型は総称して standard integer type (標準整数型)と、拡張符号付き整数型と拡張符号無し整数型は総称して extended integer type (拡張整数型)と呼ばれます。"
- §.9.1 補足 43) : それ故に、列挙(7.2)は整数値ではありません。しかしながら、列挙は 4.5 で指定されているとおり、==int, unsigned int, long、または、unsigned long== 整数型で促進され得ます。
- 全ての基本型について(キャラクター型を除いて)、オブジェクト表現の全てのビットは値表現を取ります。
- char 型のオブジェクトは厳密に 8 ビットであるべきです。
- 符号付き整数型を持つオブジェクトの値は二重補完表現を使って格納されるべきです。
基本型は以下のように実装によって提供される対応した値クラス型にマップします。
- signed char は System::SByte にマップします。
- unsigned char は System::Byte にマップします。
- もし、素の char が符号型だった場合には、char は System::SByte にマップします。さもなくば、それは System::Byte にマップします。
- 他の全ての基本型について、マッピングは実装定義です。
bool 値の表現 false は全てのビットが 0 であるべきです。
標準 C++ において、基本型はクラス型であるとは考えられていません。
しかしながら、C++/CLI ではそれぞれ基本型が実装によって決定される CLI クラスにマップするべきであるとして全ての基本型にクラス・メンバを導入します。
C++/CLI では、メンバ選択演算子が基本型の演算に適用されたとき、または、スコープ解決演算子が typedef や基本型のキーワードに適用されたとき、メンバ選択演算子やスコープ解決演算子を含む演算のスコープの中で、基本型はクラス型として扱われます。
[注記:もし、基本型が unsigned int のような一つよりも多いキーワードで表現されている場合、スコープ解決演算子は typedef か静的メンバにアクセスする CLI クラス名に適用されるべきです。]
メンバ選択演算子やスコープ解決演算子が使われるやいなや、C++/CLI はメンバを解決するために基本型と等価の値クラスを使います。
標準 C++ においては、基本型へのメンバ・アクセスとスコープ解決は許されていないので、標準C++ のクラスと非クラス型の間を区別する全てのシナリオは常に基本型を非クラスとして考えるでしょう。
[例:次の例では、スコープ解決演算子は int キーワードに CLI 値クラス型に結びつけられた名前 Parse を検索し、その処理を結果とします。
int 型の演算 x に宛われるメンバ選択演算子は CLI 値クラス型に関連する名前 ToString を探し出し結果とします。
int x = int::Parse("42");
String^ s = x.ToString();
]
12.2 クラス型
ref クラス型、値クラス型、インターフェイス型、そして、デリゲート型はブロック・スコープで宣言されるべきではありません。
12.2.1 値クラス
[注記:値クラスはフィールド、メンバ関数、そして、ネスト型を含むデータ構造です。
そのほかのクラス型と違って、値クラスはユーザー定義デストラクタ、ファイナライザ、デフォルト・コンストラクタ、コピー・コンストラクタ、ないし、コピー代入演算子をサポートしません。
値クラスは CLI 実行エンジンが効果的に値クラス・オブジェクトのコピーを行えるよう設計されています。
全ての値クラス型は暗黙のうちに System::ValueType クラスを継承しており、それを介して、System::Object クラスを継承します。
System::ValueType はそれ自身は値クラス型ではありません。むしろ、それは ref クラスであり、それから、全ての値クラス型が自動的に派生されます。]
値クラスについては
§22 で記述されています。]
12.2.2 Ref クラス
[注記:ref クラスはフィールドとメンバー関数(関数、プロパティ、イベント、演算子、インスタンス・コンストラクタ、デストラクタ、そして、静的コンストラクタ)、そして、ネスト型を含んだデータ構造を定義します。
ref クラスは継承をサポートします。ref クラスのインスタンスは
new-expression(new 演算子)(
§15.4.6 )を使って作成されます。
ref クラスについては
§21 で記述しています。]
12.2.3 インターフェイス・クラス
[注記:インターフェイス型は契約を定義します。あるインターフェイスを実装した ref クラスや値クラスはその契約を堅持するべきです。
インターフェイスは複数の基底インターフェイスから継承でき、ref クラスや値クラスは複数のインターフェイスを実装することが可能です。
インターフェイス・クラスについては
§25 で記述しています。]
12.2.4 デリゲート型
[注記:デリゲート型は一つ以上の関数と、対応するオブジェクトのインスタンスも参照しているインスタンス関数を参照するデータ構造です。
デリゲート型は
§27 で記述しています。]
12.3 型宣言子
標準C++(§3.5/3 )は次のように拡大されます。
変換済みパラメータ型の結果リストと省略記号の存在、不在は関数の parameter-type-list(パラメータ型リスト)です。
12.3.1 ロウ型
raw type(ロウ型)はクラスや基本型です。[注意:これは"へのハンドル"と"へのポインタ"を除外しています。]
12.3.2 ポインタ型
パラメータ配列(
§18.4 )を取る関数へのポインタを宣言することが可能です。
[例:
void F(double, ... array<int>^);
void (*p)(double, ... array<int>^) = &F;
]
ネイティブ・ポインタはオブジェクトがピンを刺されない限り(
§12.3.7 )、CLIヒープ上のオブジェクトを指し示すことはできません。
12.3.3 ハンドル型
任意のCLIクラス型 T に対して、宣言 T^ h は型 T へのハンドル h を宣言し、h が指し示すことができるオブジェクトは CLI ヒープ上に存在します。
ハンドルは追跡し、再結合し、そして、全ての CLI ヒープに基づくオブジェクトのみを指し示すことができます。
[注意:一般に、ネイティブ・ヒープにポインタが指すように、ハンドルは gc ヒープを指します。]
ハンドルのデフォルトの初期値は nullptr であるべきです。
CLI クラス型のオブジェクトは gcnew を通じて CLI ヒープ上に領域確保され、そのようなオブジェクトはハンドルによって参照されます。[例:
R^ r1 = gcnew R; // CLIヒープ上にオブジェクトを領域確保
R^ r2 = r1; // ハンドル r1 と r2 は同一オブジェクトを指す
]
オブジェクトが gcnew を使って領域確保された場合、オブジェクトは決して破壊( delete の使用や明示的なデストラクタ呼び出し)されることはなく、そのオブジェクトのデストラクタは決して働くことはありません。しかしながら、ガベージ・コレクタはオブジェクトのメモリを再利用し、もしあれば、ファイナライザが働くでしょう。[例:
{ // CLIヒープ上にオブジェクトを領域確保
R^ r3 = gcnew R;
} // オブジェクトはガベージ・コレクトに回され、ファイナライザが
// 実行されるがデストラクタは実行されないだろう
]
ポインタと違って、ハンドルは追跡します。それはCLIヒープ・ベースのオブジェクトはガベージ・コレクタによって参照先が移動するため、ハンドルの値が変わることかもしれないからです。これは次のことを暗示します。
- ハンドルは void* に、また、void* から変換することはできない。(ハンドルは、しかしながら、Object^ に、から、変換することができます。)[注意:void^ 型は存在しません。]
- ハンドルは整数型に、整数型から、変換することはできない。(ハンドルはガベージ・コレクタから隠れることはできません。)
- ハンドルは順序立てられない。
- ハンドルは CLI ヒープ・ベース・オブジェクトのみを指し示すことができる。
[例:
R^ r4 = new R; // ok
Object^ o = r4; // ok, r4 と r5 は同じオブジェクトを指す
R^ r5 = dynamic_cast<R^>(o); // エラー、整数に変換できない
long l = reinterpret_cast<long>(r5); // エラー、整数から変換できない
std::set<R^> s; // エラー、R^ は less による比較ができない
]
同一のCLIヒープ・ベース・オブジェクトへの全てのハンドルは、そのオブジェクトがガベージ・コレクタに移動させられたとしても、等価に比較します。
ハンドルは任意の保存期間を持つことができます。
nullptr 値を持つハンドルの表現は全てのビットが 0 であるべきです。
12.3.4 ヌル型
ヌル型は
null-literal(ヌル文字表現) nullptr (ヌル値定数としても参照されます)をサポートするためだけに存在する特殊型です。
この型はインスタンスを作ることはできません。
この型の値を得るただ一つの方法は、その型がヌル型である nullptr リテラルを通してのみです。
12.3.5 参照型
ネイティブ参照は任意の左辺値にバインドできます。
CLI ヒープ上のオブジェクトはガベージ・コレクタによって移動しうるので、その配置位置は追跡されなければなりません。そのため、そのようなオブジェクトへの参照は
tracking reference (追跡参照)( % ) と呼ばれ、任意の gc 左辺値にバインドすることができます。
CLI ヒープ上でないオブジェクトは(オブジェクトがネイティブ・クラスやピンニング・ポインタ、内部ポインタのインスタンスである場合)いつでも決定的であり、そのインスタンスは左辺値です。
[注意:そのため、ネイティブ・クラスは gc 左辺値を取るコピー代入演算子やコピー・コンストラクタを必要としません。
ネイティブ・クラス型のインスタンスはCLIヒープ上に決して領域確保されないので、 N% は安全にこれらの関数を通過することができます。
N% は最初に左辺値であるため、N% のアドレスはネイティブ・ポインタを結果とし、内部ポインタではありません。]
[注意:左辺値から gc 左辺値への標準変換があるので、追跡参照は故に任意の gc 左辺値や左辺値にバインドできます。]
任意の型 T について、宣言 T% r は型 T への追跡参照 r を宣言します。[例:
R^ h = gcnew R; // CLIヒープに領域確保
R% r = *h; // ref 型クラスへの追跡参照を結びつける
void f(V% r);
f(*gcnew V); // 追跡参照を値クラスに結びつける
]
追跡参照は ref クラス型、cv-qualified 値クラス型、cv-qualified ハンドル型、cv-qualified ネイティブ・クラス型、ないし、cv-qualified ネイティブ・ポインタのインスタンスを参照できます。そのほかの型を参照する追跡参照を含むプログラムは不正です。
通常の参照と同様に、追跡参照は再拘束できません。一度設定されたら、その値は変更できません。
自動的でない保存期間を持つ追跡参照を含んだプログラムは不正です。(これは追跡参照をデータ・メンバとして持つことをあらかじめ防ぎます。)[注意:この制限は直接的に CLI の制限を反映しています。なぜなら、一般に追跡参照とは CLI マネージド・ポインタの項目の実装だからです。]
値型 V のインスタンス v が与えられたとして、もし、V の基底クラスが参照だった場合、v は参照初期化のオブジェクトとして使われることはできません。(それは、v は System::Object% や、System::ValueType% やV を実装する任意のインターフェイスの参照に参照拘束できません。)[根拠:この理由はそのような参照拘束はボックス化を必要としており、まだボックス化値に拘束されている参照はオリジナルの値よりむしろ参照拘束の目的を破棄するためです。]
メタデータの詳細は、
§34.2.1 を参照のこと。
12.3.6 interior (内部)ポインタ
ガベージ・コレクタは CLI ヒープにあるオブジェクトの移動を許しています。そのようなオブジェクトに正確に参照するポインタであるために、実行環境はそのオブジェクトの新しい位置へのポインタに更新する必要があります。
interior pointer(内部ポインタ)(interior_ptr を使って宣言される)はこの振る舞いで更新されるポインタです。
メタデータの詳細は
§34.2.2 を参照のこと。
12.3.6.1 定義
コンパイラは内部ポインタを次のように処理します。
- コンパイラは名前 interior_ptr を現在のコンテキスト中で検索を実行する。
- もし、その名前が曖昧でなく ::cli::interior_ptr を参照しているか、名前が見つからなかった時には、演算はコンパイラによって以下の文法によって実行され、ここにあるように指定されたルールによって解釈される。
interior_ptr < type-id >
内部ポインタは明示的、暗黙のうちに auto
storage-class-specifier(保存クラス指定子)を持つべきです。内部ポインタはパラメータとして、返値として使うことができます。
内部ポインタはクラス・メンバや基底クラスであるべきではありません。
明示的な初期値を持っていない内部ポインタのデフォルト初期値は nullptr であるべきです。
12.3.6.2 目標となる型拘束
式 interior_ptr<T> 中で、ターゲット型 T は cv-qualified 値クラス型、cv-qualified ハンドル型、cv-qualified ネイティブ・クラス型、ないし、cv-qualified ネイティブ・ポインタであるべきです。そのほかのターゲット型を含むプログラムは不正です。[例:
interior_ptr<int> p1; // OK
interior_ptr<int*> p2 = nullptr; // OK
interior_ptr<System::String> p3; // エラー、String は ref 型クラス
interior_ptr<System::String^> p4; // OK :ref 型クラスへのハンドル
interior_ptr<interior_ptr<int> > p5; // OK
interior_ptr<int^> p6 = nullptr; // OK
]
12.3.6.3 操作
内部ポインタはネイティブ・ポインタと同様に、標準C++によって定義された操作のセットを含むことができます。
[注意:これは比較やポインタ演算を含みます。]
12.3.6.4 データ・アクセス
内部ポインタはデータ・アクセスに通常のポインタ構文を示します。
- 演算子 -> は内部ポインタによって指し示される CLI ヒープ・ベース・オブジェクトのメンバ・アクセスに使われます。
- 演算子 * は内部ポインタの参照外しに使われます。
[例:
value struct V {
int data;
};
V v;
interior_ptr<V> pv = &v;
pv->data = 42;
interior_ptr<int> pi = &v.data;
assert( *pi == 42 );
]
内部ポインタのアドレスを取ることはネイティブ・ポインタを明け渡します。
内部ポインタはCLIヒープの内側でオブジェクトを指し示すことができます。
その様な、内部ポインタに指し示されるオブジェクトのアドレスを取ることは T* に変換できない内部ポインタを明け渡します。
[例:
value struct V {
int data;
};
V v;
interior_ptr<V> pv = &v;
V** p = &pv; // エラー
interior_ptr<V>* pi = &pv; // OK、pv はスタック上にあり、左辺値
int* p2 = &(pv->data); // エラー
int* p3 = &(v.data); // OK、v はスタック上にあり、v.data は左辺値
]
12.3.6.5 this ポインタ
値クラス V の非静的メンバ関数の本文中で、this は型 interior_ptr<T> の右辺値演算であり、その値はその関数が呼ばれた CLI ヒープ・ベース・オブジェクトのアドレスです。
[例:
value struct V {
int data;
void f();
};
void V::f() {
interior_ptr<V> pv1 = this; // OK
V* pv2 = this; // エラー
}
]
12.3.7 ピンニング・ポインタ
通常、ガベージ・コレクタはCLIヒープ上にあるオブジェクトの移動を許しています。
しかしながら、その様な移動は、オブジェクト単位で、一時的にブロックすることが可能です。
ピンイング・ポインタ(pin_ptr で宣言される)はポインタが指し示す CLI ヒープ・ベース・オブジェクトの移動をガベージ・コレクタから妨害します。
これはコードに CLI ヒープの領域で、そのヒープを腐らせることなく、ランタイムの制御下でなくメモリを操作することを可能とします。
ピンニング・ポインタは内部ポインタから初期化されることは可能ですが、ピンニング・ポインタの値は決して実行時に変わることはありません。
ピンニング・ポインタはメモリ上どこででもオブジェクトを指し示すことができ、オブジェクトが CLI ヒープ上にある必要はありません。
メタデータの詳細は、
§34.2.3 を参照のこと。
12.3.7.1 定義
コンパイラはピンニング・ポインタを以下のように進めます。
- コンパイラは現在のコンテキスト上に名前 pin_ptr の検索を実行します。
- もし、名前が曖昧さ無しに ::cli::pin_ptr を示しているか、名前が見つからなかった時には、演算はコンパイラによって次のような構文によって名前検索を進め、ここに指定されたルールによって解釈されます。
pin_ptr < type-id >
ピンニング・ポインタは型
type-specifier へのハンドルである内部ポインタです。それは
type-id です。
ピンニング・ポインタは明示的か、暗黙のうちに、auto
storage-class-specifier を持つべきです。ピンニング・ポインタはパラメータや返値として使われるべきではありません。
[注意:ピンニング・ポインタは内部ポインタなので、明示的な初期化値を持たないピンニング・ポインタのデフォルト初期値は nullptr です。]
12.3.7.2 目標となる型拘束
ピンニング・ポインタの標的型制限は interior ポインタ(
§12.3.6.2 )と同様です。
12.3.7.3 操作
ピンニング・ポインタの形作ることができる操作は、ピンニング・ポインタはキャスト中に含まれることができないことを除いて interior ポインタ(
§12.3.6.3 )と同様です。
12.3.7.4 データ・アクセス
二つの例外として、ピンニング・ポインタは interior ポインタ(
§12.3.6.4 )と同じ意味として同様のデータ・アクセスに従います。
value struct V {
int data;
void f();
};
void V::f() {
int* pi;
interior_ptr<V> ipv = this;
pi = &(ipv->data); // エラー
pin_ptr<V> ppv = this;
pi = &(ppv->data); // OK
V* pv;
pv = ipv; // エラー
pv = ppv; // OK
}
V v;
pin_ptr<V> pv = &v;
V** p = &pv;
int* pi = &pv->data;
]
12.3.7.5 ピンされている期間
ピンを刺すポインタが初期化されたり、オブジェクトのアドレスが代入されるやいなや、そのオブジェクトはそのCLIヒープ中の配置位置に留まることを保証されます。
もし、そのピンを刺したポインタが他のオブジェクトを指し示すようにされた時、そのオブジェクトはCLIヒープ中の配置位置に留まることが保証され、以前指し示していたオブジェクトはもうピンを刺されていると考えず、ガベージ・コレクタがそのオブジェクトを移動することを許します。もし、ピンを刺したポインタが nullptr を代入されたら、先ほどまでポイントされていたオブジェクトはもうピンを刺されていないものと見なされます。
ピンを刺されたポインタが定義されているブロックから抜け出た時、ピンニング・ポインタに指し示された任意のオブジェクトはピンニング・ポインタによってもうピンを刺されているとは見なされません。しかしながら、他のピンニング・ポインタはまだピンを刺し続けていることでしょう。
クラス System::Runtime::InteropServices::GCHandle によって提供される機能を例外として、もし、ピンニング・ポインタが CLI ヒープ・ベース・オブジェクトを指していなければ、オブジェクトがピンを刺されていると仮定することは安全ではありません。
[例:
ref struct R {
int data;
};
R^ r = gcnew R;
{
pin_ptr<int> ppi = &r->data; // r に参照されているオブジェクトがピンを打たれた
}
// ppi の親ブロックが終了、オブジェクトも自由に再配置できる
]
12.3.8 ネイティブ配列
CLI クラス型やハンドル型を要素に持つネイティブ配列を含むプログラムは不正です。[注意:そのような型の要素を許す配列型は混合型(
§23 )になります。]
ネイティブ配列はその親アセンブリについてローカル(それは internal になります)であり、その型は検証不能です。故に、パラメータとしてネイティブ配列型を持つ仮想関数は、他のアセンブリから上書きされることはできません。
メタデータの詳細は、
§34.2.4 を参照のこと。
12.4 上層型認識
非ネストクラス、インターフェイス、デリゲート、または列挙型の定義はオプションとしてクラス、インターフェイス、デリゲート、列挙型のアクセス可能性を指定することができます。
top-level-visibility:
public
private
public 上層型認識(top-level-type-visibility)修飾子はアセンブリの外部に対して視認可能となる非ネスト型のクラス、インターフェイス、デリゲート、または列挙型を示しています。
反対に、
private 上層型認識(top-level-type-visibility)修飾子はそのクラス、インターフェイス、デリゲート、または列挙型が、その親アセンブリの外側からでは視認できないことを指定します。
しかしながら、private 型はその親アセンブリの内部では視認可能です。
クラス、インターフェイス、デリゲート、または列挙型のデフォルトの視認可能性は
private です。
[例:
public class VisibleClass {}; // アセンブリ外部からも見える
private class InternalClass {}; // アセンブリ内部でのみ見える
]
他の型定義中にネストされたクラス、インターフェイス、または列挙型などの定義は、その型で指定されたアクセス可能性を持ちます。
ネストされた型定義に
上層型認識(top-level-type-visibility)修飾子を使うことはプログラムとして不正です。