オブジェクト
C++ のプログラムは、オブジェクトを、作成、破棄、参照、アクセス、および操作します。
C++ では、オブジェクトは記憶域の領域であり、以下のものを持ちます。
- サイズ (sizeof で調べられます)
- アライメント要件 (alignof で調べられます)
- 記憶域期間 (自動、静的、動的、スレッドローカル)
- 生存期間 (記憶域期間または一時オブジェクトによって縛られます)
- 型
- 値 (不定の場合もあります、例えばデフォルト初期化された非クラス型の場合)
- オプションで、名前
値、参照、関数、列挙子、型、非静的クラスメンバ、ビットフィールド、テンプレート、クラスまたは関数テンプレートの特殊化、名前空間、パラメータパック、および this は、オブジェクトではありません。
変数は、非静的データメンバでないオブジェクトまたは参照であり、宣言によって導入されます。
オブジェクトは、定義、 new 式、 throw 式によって、および共用体のアクティブメンバを変更するとき、および一時オブジェクトが必要な場面で、作成されます。
目次 |
[編集]オブジェクト表現と値表現
T
型のオブジェクトの場合、オブジェクト表現は、 T
のオブジェクトと同じアドレスで始まる unsignedchar (または std::byte) 型のオブジェクト sizeof(T) 個の並びです。
オブジェクトの値表現は、その型 T
の値を保持するビット列の集合です。
TriviallyCopyable な型の場合、値表現はオブジェクト表現の一部です。 これは、同じ値を持つ別のオブジェクトを生成するためには、記憶域内のオブジェクトが占めるバイト列をコピーすれば十分であることを意味します (ただし、その値がその型のトラップ表現 (例えば SNaN ("signalling not-a-number") 浮動小数点値や NaT ("not-a-thing") 整数など) であり、それを CPU にロードするとハードウェア例外が発生する場合は除きます)。
逆は真であるとは限りません。 異なるオブジェクト表現を持つ TriviallyCopyable な型の2つのオブジェクトが同じ値を表現することがあります。 例えば、同じ特別な値 NaN を表現する浮動小数点のビットパターンは複数あります。 もっと一般的な例は、オブジェクト表現の一部のビットが値表現に一切寄与しないケースです。 アライメント要件やビットフィールドのサイズなどを満たすために、そのようなビットがパディングとして導入されることがあります。
#include <cassert>struct S {char c;// 1バイトの値// 3バイトのパディング (alignof(float) == 4 と仮定しています)float f;// 4バイトの値 (sizeof(float) == 4 と仮定しています)bool operator==(const S& arg)const{// 値ベースの等価比較return c == arg.c&& f == arg.f;}}; void f(){assert(sizeof(S)==8); S s1 ={'a', 3.14}; S s2 = s1;reinterpret_cast<unsignedchar*>(&s1)[2]='b';// パディングの2バイトめを変更します。assert(s1 == s2);// 値は変わりません。}
char、 signedchar、および unsignedchar 型のオブジェクト (それらが過剰なサイズのビットフィールドである場合は除きます) の場合は、そのオブジェクト表現のすべてのビットが値表現に寄与することが要求され、有り得るすべてのビットパターンが異なる値を表現します (パディングやトラップビット、複数の表現は許されません)。
[編集]部分オブジェクト
オブジェクトは他のオブジェクト (部分オブジェクトと言います) を含むことができます。 これには以下のものがあります。
- メンバオブジェクト
- 基底クラス部分オブジェクト
- 配列要素
別のオブジェクトの部分オブジェクトでないオブジェクトは完全オブジェクトと言います。
部分オブジェクトは、以下のいずれかである場合、潜在的にオーバーラップします。
- 基底クラス部分オブジェクト
[[no_unique_address]]
属性付きで宣言された非静的データメンバ
完全オブジェクト、メンバオブジェクト、および配列要素は、基底クラス部分オブジェクトと区別するために、最も派生したオブジェクトとも言います。 ビットフィールドでなく、 [[no_unique_address]]
付き(C++20以上)でない、最も派生したオブジェクトのサイズは、ゼロでないことが要求されます(基底クラス部分オブジェクトのサイズは、たとえ [[no_unique_address]]
がなくとも(C++20以上)、ゼロであることがあります。 空の基底の最適化を参照してください)。
生存期間がオーバーラップする任意の2つの (ビットフィールドでない) オブジェクトは、一方が他方の部分オブジェクトであるのなく、一方が他方のための記憶域を提供するのでなく、それらが同じ完全オブジェクト内の別の型の部分オブジェクトでありかつ一方がサイズゼロの部分オブジェクトであるのでない限り、異なるアドレスを持つことが保証されます。
staticconstchar c1 ='x';staticconstchar c2 ='x';assert(&c1 !=&c2);// 同じ値、異なるアドレス
[編集]多相オブジェクト
少なくとも1つの仮想関数を宣言または継承するクラス型のオブジェクトは多相オブジェクトです。 それぞれの多相オブジェクト内に、処理系は追加の情報を格納します (すべての既存の処理系で、これはポインタ1個です (最適化によって除去された場合は除きます))。 これは、それが使用された式に関わらず、そのオブジェクトの作成に使用された型を実行時に調べるために、仮想関数の呼び出しおよび RTTI 機能 (dynamic_cast および typeid) によって、使用されます。
非多相オブジェクトの場合、値の解釈はそのオブジェクトが使用された式から調べられ、コンパイル時に決定されます。
#include <iostream>#include <typeinfo>struct Base1 {// 多相型。 仮想メンバを宣言しています。virtual ~Base1(){}};struct Derived1 : Base1 {// 多相型。 仮想メンバを継承しています。}; struct Base2 {// 非多相型。};struct Derived2 : Base2 {// 非多相型。}; int main(){ Derived1 obj1;// object1 は型 Derived1 を使用して作成されました。 Derived2 obj2;// object2 は型 Derived2 を使用して作成されました。 Base1& b1 = obj1;// b1 はオブジェクト obj1 を参照します。 Base2& b2 = obj2;// b2 はオブジェクト obj2 を参照します。 std::cout<<"Expression type of b1: "<<typeid(decltype(b1)).name()<<' '<<"Expression type of b2: "<<typeid(decltype(b2)).name()<<'\n'<<"Object type of b1: "<<typeid(b1).name()<<' '<<"Object type of b2: "<<typeid(b2).name()<<'\n'<<"size of b1: "<< sizeof b1 <<' '<<"size of b2: "<< sizeof b2 <<'\n';}
出力例:
Expression type of b1: Base1 Expression type of b2: Base2 Object type of b1: Derived1 Object type of b2: Base2 size of b1: 8 size of b2: 1
[編集]厳密なエイリアシング
オブジェクトの作成に使用された型と異なる型の式を使用してそのオブジェクトにアクセスすることは、多くの場合、未定義動作です。 例外の一覧および例については reinterpret_cast を参照してください。
[編集]アライメント
すべてのオブジェクト型にはアライメント要件という性質があります。 これは、その型のオブジェクトを連続的に割り当てることができるアドレス間のバイト数を表す、整数値 (std::size_t 型で、常に2の乗数) です。 何らかのバッファ内の適切にアラインされたポインタを取得するために、ポインタアライメント関数 std::align が使用できます。 適切にアラインされた記憶域を取得するために、 std::aligned_storage が使用できます。
それぞれのオブジェクト型は、その型のすべてのオブジェクトに対して、そのアライメント要件を課します。 alignas を用いて、より厳しいアライメント (より大きなアライメント要件) を要求できます。
クラスのすべての非静的メンバのアライメント要件を満たすために、そのいくつかのメンバの後にパディングが挿入されることがあります。
#include <iostream> // S 型のオブジェクトは任意のアドレスに割り当てられます。// S.a と S.b の両方を任意のアドレスに割り当てられるためです。struct S {char a;// サイズ1、アライメント1char b;// サイズ1、アライメント1};// サイズ2、アライメント1 // X 型のオブジェクトは4バイト境界に割り当てなければなりません。// X.n を4バイト境界に割り当てなければならないためです。// これは int のアライメント要件が (通常は) 4であるためです。struct X {int n;// サイズ4、アライメント4char c;// サイズ1、アライメント1// 3バイトのパディング};// サイズ8、アライメント4 int main(){std::cout<<"sizeof(S) = "<< sizeof(S)<<" alignof(S) = "<< alignof(S)<<'\n';std::cout<<"sizeof(X) = "<< sizeof(X)<<" alignof(X) = "<< alignof(X)<<'\n';}
出力例:
sizeof(S) = 2 alignof(S) = 1 sizeof(X) = 8 alignof(X) = 4
最も弱いアライメント (最も小さなアライメント要件) は char、 signedchar、および unsignedchar のアライメントであり、これは1です。 あらゆる型で最も大きな基本アライメントは std::max_align_t のアライメントです。 alignas を用いて型のアライメントを std::max_align_t よりも厳しく (大きく) した場合、それは拡張アライメント要件を持つ型と言います。 拡張アライメントの型、または拡張アライメントの非静的データメンバを持つクラス型は、オーバーアラインされた型です。 new 式、 std::allocator::allocate、および std::get_temporary_buffer がオーバーアラインされた型をサポートするかどうかは、処理系定義です。 オーバーアラインされた型で実体化された Allocator は、コンパイル時に実体化に失敗すること、実行時に std::bad_alloc を投げること、非対応のアライメント要件を黙って無視すること、またはそれらを正しく処理することが許されます。
[編集]関連項目
オブジェクト の C言語リファレンス |