Object
C++プログラムは、 objects を生成、破棄、参照、アクセス、操作します。
C++におけるオブジェクトは、
-
サイズ(
sizeofで決定可能); -
アライメント要件(
alignofで決定可能); - ストレージ期間 (自動、静的、動的、スレッドローカル);
- 生存期間 (ストレージ期間または一時オブジェクトによって制限される);
- 型 ;
- 値(不定の場合あり、例: デフォルト初期化 された非クラス型);
- オプションで 名前 。
以下のエンティティはオブジェクトではありません:値、参照、関数、列挙子、型、非静的クラスメンバ、テンプレート、クラスまたは関数テンプレートの特殊化、名前空間、パラメータパック、および this 。
variable は、非静的データメンバーではないオブジェクトまたは参照であり、 declaration によって導入されます。
目次 |
オブジェクト生成
オブジェクトは明示的に、 定義 、 new 式 、 throw 式 、 union のアクティブメンバーの変更、および 一時オブジェクト を必要とする式の評価によって作成されます。作成されたオブジェクトは、明示的なオブジェクト作成において一意に定義されます。
暗黙の生存期間型のオブジェクトは、以下の方法でも暗黙的に作成できます
- 定数評価中を除き、型 unsigned char または std::byte (C++17以降) の配列の生存期間を開始する操作において、その場合そのようなオブジェクトは配列内に作成され、
- 以下の割り当て関数の呼び出しにおいて、その場合そのようなオブジェクトは割り当てられたストレージ内に作成されます:
-
- operator new (定数評価中を除く)
- operator new[] (定数評価中を除く)
- std::malloc
- std::calloc
- std::realloc
| (C++17以降) |
- 以下の関数への呼び出し オブジェクト表現 をコピーする場合、そのようなオブジェクトは宛先の記憶域領域または結果内で作成されます:
| (C++20以降) |
|
(C++23以降) |
同じ記憶域の領域にゼロ個以上のオブジェクトが作成される可能性があります。これは、そのようにすることがプログラムに定義された動作を与える限りにおいて許されます。そのような作成が不可能な場合(例えば、競合する操作による場合)、プログラムの動作は未定義です。複数のそのような暗黙的に作成されたオブジェクトの集合がプログラムに定義された動作を与える場合、どのオブジェクトの集合が作成されるかは未規定です。言い換えれば、暗黙的に作成されるオブジェクトが一意に定義される必要はありません。
ストレージの指定された領域内でオブジェクトを暗黙的に作成した後、一部の操作は 適切に作成されたオブジェクト へのポインタを生成します。適切に作成されたオブジェクトは、ストレージの領域と同じアドレスを持ちます。同様に、プログラムに定義された動作を与えるポインタ値が存在しない場合にのみ動作は未定義となり、プログラムに定義された動作を与える複数の値が存在する場合、どのポインタ値が生成されるかは未規定です。
#include <cstdlib> struct X { int a, b; }; X* MakeX() { // 可能な定義済み動作の1つ: // std::mallocの呼び出しは暗黙的に型Xのオブジェクトと // そのサブオブジェクトaおよびbを作成し、そのXオブジェクトへのポインタを返す X* p = static_cast<X*>(std::malloc(sizeof(X))); p->a = 1; p->b = 2; return p; }
std::allocator::allocate への呼び出し、または union 型の暗黙的に定義されたコピー/ムーブ特殊メンバ関数によってもオブジェクトを生成できます。
オブジェクト表現と値表現
一部の型とオブジェクトには object representations と value representations があり、それらは以下の表で定義されています:
| エンティティ | オブジェクト表現 | 値表現 |
|---|---|---|
完全オブジェクト型
T
|
T
型の非
ビットフィールド
完全オブジェクトが占める
N
個の
unsigned
char
オブジェクトのシーケンス(
N
は
sizeof
(
T
)
に等しい)
|
T
型の値を表現するために使用される
T
のオブジェクト表現内のビットの集合
|
T
型の非ビットフィールド完全オブジェクト
obj
|
T
のオブジェクト表現に対応する
obj
のバイト列
|
T
の値表現に対応する
obj
のビット列
|
| ビットフィールドオブジェクト bf | bf が占める N ビットのシーケンス( N はビットフィールドの幅) | bf の値を表現するために使用される bf のオブジェクト表現内のビットの集合 |
型またはオブジェクトのオブジェクト表現において、値表現の一部ではないビットは パディングビット です。
TriviallyCopyable 型の場合、値表現はオブジェクト表現の一部であり、ストレージ内のオブジェクトが占有するバイト列をコピーするだけで同じ値を持つ別のオブジェクトを生成できることを意味します(ただし、オブジェクトが潜在的にオーバーラップする部分オブジェクトである場合、またはその値が型の トラップ表現 であり、CPUにロードするとハードウェア例外(SNaN(シグナリング非数)浮動小数点値やNaT(非実在)整数など)が発生する場合を除く)。
ほとんどの実装ではトラップ表現、パディングビット、または整数型の複数表現は許可されていませんが、例外があります。例えばItanium上の整数型の値は トラップ表現となる可能性があります 。
逆は必ずしも真ならず:同じ値を持つ TriviallyCopyable 型の二つのオブジェクトが異なるオブジェクト表現を持つ場合がある。例えば、複数の浮動小数点ビットパターンが同じ特殊値 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<unsigned char*>(&s1)[2] = 'b'; // 一部のパディングビットを変更 assert(s1 == s2); // 値は変更されていない }
char 型、 signed char 型、および unsigned char 型のオブジェクトについて(過大サイズの ビットフィールド でない限り)、オブジェクト表現のすべてのビットは値表現に参加することが要求され、可能な各ビットパターンは異なる値を表します(パディングビット、トラップビット、または複数の表現は許可されません)。
サブオブジェクト
オブジェクトは subobjects を持つことができます。これらには以下が含まれます
- メンバーオブジェクト
- 基底クラスのサブオブジェクト
- 配列要素
他のオブジェクトの部分オブジェクトではないオブジェクトは、 complete object と呼ばれます。
完全オブジェクト、メンバ部分オブジェクト、または配列要素が クラス型 である場合、その型は基底クラスの部分オブジェクトのクラス型と区別するために 最派生クラス と見なされます。最派生クラス型または非クラス型のオブジェクトは 最派生オブジェクト と呼ばれます。
クラスに対して、
その 潜在的に構築される部分オブジェクト と呼ばれます。
サイズ
サブオブジェクトは、それが基底クラスのサブオブジェクトである場合
または
[[
no_unique_address
]]
属性で宣言された非静的データメンバーである場合
(C++20以降)
、
潜在的にオーバーラップするサブオブジェクト
です。
オブジェクト obj がゼロサイズを持つ可能性があるのは、以下の条件がすべて満たされた場合に限ります:
- obj は潜在的にオーバーラップする部分オブジェクトです。
- obj は仮想メンバ関数と仮想基底クラスを持たないクラス型です。
- obj は非ゼロサイズの部分オブジェクトや非ゼロ長の無名 bit-fields を持ちません。
上記のすべての条件を満たすオブジェクト obj について:
- obj が非静的データメンバを持たない 標準レイアウト (C++11以降) クラス型の基底クラスサブオブジェクトである場合、そのサイズはゼロです。
- それ以外の場合、 obj がゼロサイズとなる状況は実装定義です。
詳細については empty base optimization を参照してください。
ゼロ以外のサイズを持つ任意の非ビットフィールドオブジェクトは、1つ以上のバイトのストレージを占有しなければならず、そのサブオブジェクトのいずれかによって(完全にまたは部分的に)占有されるすべてのバイトを含みます。オブジェクトが trivially copyable または standard-layout (C++11以降) 型である場合、占有されるストレージは連続していなければなりません。
アドレス
オブジェクトがビットフィールドまたはサイズゼロのサブオブジェクトでない限り、そのオブジェクトの アドレス は、そのオブジェクトが占有する最初の byte のアドレスである。
オブジェクトは他のオブジェクトを含むことができ、その場合、含まれるオブジェクトは元のオブジェクト内に ネストされた 状態となります。以下の条件のいずれかを満たす場合、オブジェクト a は別のオブジェクト b 内にネストされていると言います:
- a は b のサブオブジェクトです。
- b ストレージを提供します ( a に対して)。
- オブジェクト c が存在し、 a は c 内にネストされ、 c は b 内にネストされています。
オブジェクトは、以下のいずれかのオブジェクトである場合、 潜在的に非一意なオブジェクト となります:
- A string literal オブジェクト。
|
(C++11以降) |
- 潜在的に非ユニークなオブジェクトのサブオブジェクト。
任意の2つの非ビットフィールドオブジェクトで、 生存期間 が重複する場合:
- 以下のいずれかの条件が満たされる場合、それらは同じアドレスを持つ可能性があります:
-
- 一方が他方の内部にネストされている場合。
- いずれかがサイズゼロの副オブジェクトであり、それらの型が similar ではない場合。
- 両方が潜在的に非ユニークなオブジェクトである場合。
- それ以外の場合、それらは常に異なるアドレスを持ち、ストレージの互いに素なバイトを占有します。
// 文字リテラルは常に一意である static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // 常にtrue // 「r」、「s」、「il」からアクセスされる文字'x'は // 同じアドレスを持つ可能性がある(つまり、これらのオブジェクトはストレージを共有する可能性がある) static const char (&r) [] = "x"; static const char *s = "x"; static std::initializer_list<char> il = {'x'}; const bool b2 = r != il.begin(); // 未規定の結果 const bool b3 = r != s; // 未規定の結果 const bool b4 = il.begin() != &test1; // 常にtrue const bool b5 = r != &test1; // 常にtrue
ポリモーフィックオブジェクト
少なくとも1つの仮想関数を宣言または継承するクラス型のオブジェクトは、ポリモーフィックなオブジェクトです。各ポリモーフィックなオブジェクト内では、実装は追加情報(既存のすべての実装では、最適化されない限り1つのポインタ)を保存します。これは、
仮想関数
呼び出しおよびRTTI機能(
dynamic_cast
および
typeid
)によって使用され、実行時に、オブジェクトが使用されている式に関係なく、そのオブジェクトが作成された型を決定します。
非多態オブジェクトの場合、値の解釈はオブジェクトが使用される式から決定され、コンパイル時に決定されます。
#include <iostream> #include <typeinfo> struct Base1 { // polymorphic type: declares a virtual member virtual ~Base1() {} }; struct Derived1 : Base1 { // polymorphic type: inherits a virtual member }; struct Base2 { // non-polymorphic type }; struct Derived2 : Base2 { // non-polymorphic type }; int main() { Derived1 obj1; // object1 created with type Derived1 Derived2 obj2; // object2 created with type Derived2 Base1& b1 = obj1; // b1 refers to the object obj1 Base2& b2 = obj2; // b2 refers to the object obj2 std::cout << "Expression type of b1: " << typeid(decltype(b1)).name() << '\n' << "Expression type of b2: " << typeid(decltype(b2)).name() << '\n' << "Object type of b1: " << typeid(b1).name() << '\n' << "Object type of b2: " << typeid(b2).name() << '\n' << "Size of b1: " << sizeof b1 << '\n' << "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の累乗)で、この型のオブジェクトが割り当て可能な連続したアドレス間のバイト数を表します。
|
型のアライメント要件は、
|
(C++11以降) |
各オブジェクト型は、その型のすべてのオブジェクトに対してアライメント要件を課します
; より厳格なアライメント(より大きなアライメント要件を持つ)は
alignas
を使用して要求できます
(C++11以降)
。オブジェクトの型のアライメント要件を満たさないストレージにオブジェクトを作成しようとすることは未定義動作です。
クラスのすべての非静的メンバーのアライメント要件を満たすために、 class の一部のメンバーの後に padding bits が挿入される場合があります。
#include <iostream> // 型Sのオブジェクトは任意のアドレスに配置可能 // S.aとS.bの両方が任意のアドレスに配置可能なため struct S { char a; // サイズ: 1, アライメント: 1 char b; // サイズ: 1, アライメント: 1 }; // サイズ: 2, アライメント: 1 // 型Xのオブジェクトは4バイト境界に配置する必要がある // X.nが4バイト境界に配置される必要があるため // intのアライメント要件は(通常)4であるため struct X { int n; // サイズ: 4, アライメント: 4 char c; // サイズ: 1, アライメント: 1 // 3バイトのパディングビット }; // サイズ: 8, アライメント: 4 int main() { std::cout << "alignof(S) = " << alignof(S) << '\n' << "sizeof(S) = " << sizeof(S) << '\n' << "alignof(X) = " << alignof(X) << '\n' << "sizeof(X) = " << sizeof(X) << '\n'; }
出力例:
alignof(S) = 1 sizeof(S) = 2 alignof(X) = 4 sizeof(X) = 8
最も弱いアライメント(最小のアライメント要件)は char 、 signed char 、および unsigned char のアライメントであり、その値は 1 である。あらゆる型の最大の 基本アライメント は実装定義であり 、 std::max_align_t のアライメントと等しい (C++11以降) 。
基本的なアラインメントは、あらゆるストレージ期間のオブジェクトに対してサポートされています。
|
型のアラインメントが std::max_align_t よりも厳格に(大きく)設定された場合、それは 拡張アラインメント 要件を持つ型として知られています。アラインメントが拡張された型、または非静的データメンバーが拡張アラインメントを持つクラス型は オーバーアライン型 です。 Allocator 型はオーバーアライン型を正しく扱うことが要求されます。 |
(C++11以降) |
|
実装定義であるかどうか new 式 および (C++17まで) std::get_temporary_buffer がオーバーアラインド型をサポートするかどうかは実装定義である。 |
(C++11から)
(C++20まで) |
注記
C++におけるオブジェクトは、 object-oriented programming (OOP) におけるオブジェクトとは異なる意味を持ちます:
| C++におけるオブジェクト | OOPにおけるオブジェクト |
|---|---|
|
任意のオブジェクト型を持つことができる
( std::is_object を参照) |
クラス型でなければならない |
| 「インスタンス」の概念がない |
「インスタンス」の概念がある(そして
instanceof
のような「インスタンス関係」を検出するメカニズムが存在する)
|
| 「インターフェース」の概念がない |
「インターフェース」の概念がある(そしてインターフェースが実装されているかどうかを検出する
instanceof
のようなメカニズムが存在する)
|
| 仮想メンバーを介して明示的にポリモーフィズムを有効にする必要がある | ポリモーフィズムは常に有効 |
欠陥報告書
P0593R6
において、バイト配列の作成時または
allocation function
(ユーザー定義であり
constexpr
である可能性がある)の呼び出し時に、implicit object creationが発生すると考えられていました。しかし、この許可は定数評価における非決定性を引き起こし、望ましくないものであり、いくつかの側面で実装不可能でした。その結果、
P2747R2
は定数評価におけるこのようなimplicit object creationを禁止しました。我々はこの変更を、論文全体がそうではないものの、意図的に欠陥報告として扱います。
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 633 | C++98 | 変数はオブジェクトのみとされていた | 参照も変数となりうる |
| CWG 734 | C++98 |
同じスコープで定義され、同じ値を持つことが保証されている
変数が同じアドレスを持つかどうかは未規定であった |
ライフタイムが重なる場合、値に関わらず
アドレスが異なることが保証される |
| CWG 1189 | C++98 |
同じ型の2つの基底クラス部分オブジェクトが
同じアドレスを持つ可能性があった |
常に異なるアドレスを
持つ |
| CWG 1861 | C++98 |
狭い文字型のオーバーサイズビットフィールドの場合、
オブジェクト表現の全てのビットが 値表現に参加していた |
パディングビットを許可 |
| CWG 2489 | C++98 |
char
[
]
はストレージを提供できないが、オブジェクトは
そのストレージ内に暗黙的に作成されうる |
オブジェクトは
char
[
]
のストレージ内に
暗黙的に作成できない |
| CWG 2519 | C++98 | オブジェクト表現の定義がビットフィールドに対応していなかった | ビットフィールドに対応 |
| CWG 2719 | C++98 |
ミスアラインドされたストレージでのオブジェクト作成の
動作が不明確であった |
この場合の動作は
未定義である |
| CWG 2753 | C++11 |
初期化子リストのバッキング配列が
文字列リテラルとストレージを共有できるか不明確であった |
ストレージを共有可能 |
| CWG 2795 | C++98 |
ライフタイムが重なる2つのオブジェクトが同じアドレスを
持つかどうかを決定する際、いずれかがゼロサイズの部分オブジェクトの場合、 それらは類似した異なる型を持つ可能性があった |
非類似型のみを許可 |
| P0593R6 | C++98 |
以前のオブジェクトモデルは標準ライブラリが必要とする
多くの有用なイディオムをサポートせず、 Cの有効型(effective types)とも互換性がなかった |
暗黙的オブジェクト作成が追加された |
関連項目
|
C documentation
for
Object
|
|
Cドキュメント
の
Object
|