Object
C++プログラムは、オブジェクトを作成、破棄、参照、アクセス、操作します。
C++におけるオブジェクトは、以下のものを持っています。
- サイズ(
sizeofで決定可能)。 - アライメント要件(
alignofで決定可能)。 - 記憶域期間(自動、静的、動的、スレッドローカル)。
- 寿命(記憶域期間または一時オブジェクトによって区切られる)。
- type;
- 値(不定となる場合がある。例:デフォルト初期化された非クラス型)。
- オプションで、名前。
以下のエンティティはオブジェクトではありません:値、参照、関数、列挙子、型、非静的クラスメンバー、テンプレート、クラスまたは関数テンプレート特殊化、名前空間、パラメータパック、およびthis。
変数とは、宣言によって導入される、非静的データメンバーではないオブジェクトまたは参照のことです。
目次 |
[編集] オブジェクトの作成
オブジェクトは、定義、new式、throw式、共用体のアクティブメンバーの変更、一時オブジェクトを必要とする式の評価によって明示的に作成できます。明示的なオブジェクト作成では、作成されたオブジェクトは一意に定義されます。
暗黙的寿命型のオブジェクトは、以下の方法でも暗黙的に作成されます。
- 定数評価時を除き、型が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() { // One of possible defined behaviors: // the call to std::malloc implicitly creates an object of type X // and its subobjects a and b, and returns a pointer to that X object X* p = static_cast<X*>(std::malloc(sizeof(X))); p->a = 1; p->b = 2; return p; }
std::allocator::allocateの呼び出し、または共用体型の暗黙的に定義されたコピー/ムーブ特殊メンバ関数もオブジェクトを作成できます。
[編集] オブジェクト表現と値表現
一部の型とオブジェクトはオブジェクト表現と値表現を持ち、それらは以下の表で定義されています。
| エンティティ | オブジェクト表現 | 値表現 |
|---|---|---|
完全なオブジェクト型 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("signalling not-a-number")浮動小数点値やNaT("not-a-thing")整数など)。
ほとんどの実装では整数型でトラップ表現、パディングビット、または複数の表現を許可していませんが、例外もあります。たとえば、Itanium上の整数型の値はトラップ表現である場合があります。
逆は必ずしも真ではありません。TriviallyCopyable型の2つのオブジェクトが異なるオブジェクト表現を持っていても、同じ値を表現する場合があります。たとえば、複数の浮動小数点ビットパターンが同じ特殊値NaNを表現します。より一般的には、アライメント要件、ビットフィールドサイズなどを満たすためにパディングビットが導入される場合があります。
#include <cassert> struct S { char c; // 1 byte value // 3 bytes of padding bits (assuming alignof(float) == 4) float f; // 4 bytes value (assuming sizeof(float) == 4) bool operator==(const S& arg) const // value-based equality { 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'; // modify some padding bits assert(s1 == s2); // value did not change }
char、signed char、およびunsigned charのオブジェクト(オーバーサイズのビットフィールドではない限り)では、オブジェクト表現のすべてのビットが値表現に寄与し、可能な各ビットパターンは異なる値を表す必要があります(パディングビット、トラップビット、または複数の表現は許可されません)。
[編集] サブオブジェクト
オブジェクトはサブオブジェクトを持つことができます。これには以下が含まれます。
- メンバーオブジェクト
- 基底クラスサブオブジェクト
- 配列要素
他のオブジェクトのサブオブジェクトではないオブジェクトは完全オブジェクトと呼ばれます。
完全オブジェクト、メンバーサブオブジェクト、または配列要素がクラス型である場合、その型は、任意の基底クラスサブオブジェクトのクラス型と区別するために最も派生したクラスと見なされます。最も派生したクラス型または非クラス型のオブジェクトは最も派生したオブジェクトと呼ばれます。
クラスの場合、
は、その潜在的に構築されるサブオブジェクトと呼ばれます。
[編集] サイズ
サブオブジェクトは、基底クラスサブオブジェクトである場合、または[[no_unique_address]]属性で宣言された非静的データメンバーである場合(C++20以降)、潜在的にオーバーラップするサブオブジェクトです。
オブジェクトobjがゼロサイズになる可能性があるのは、以下のすべての条件が満たされた場合のみです。
- objは潜在的にオーバーラップするサブオブジェクトです。
- objは仮想メンバ関数および仮想基底クラスを持たないクラス型です。
- objは、ゼロでないサイズのサブオブジェクト、またはゼロでない長さの無名ビットフィールドを持ちません。
上記のすべての条件を満たすオブジェクトobjの場合
- objが非静的データメンバーを持たない標準レイアウト(C++11以降)クラス型の基底クラスサブオブジェクトである場合、そのサイズはゼロです。
- それ以外の場合、objがゼロサイズになる状況は実装定義です。
詳細については、空の基底クラス最適化を参照してください。
ゼロでないサイズの非ビットフィールドオブジェクトは、そのサブオブジェクトによって(完全にまたは部分的に)占有されるすべてのバイトを含め、1バイト以上の記憶域を占有しなければなりません。オブジェクトがトリビアルコピー可能または標準レイアウト(C++11以降)型である場合、占有される記憶域は連続していなければなりません。
[編集] アドレス
オブジェクトがビットフィールドまたはゼロサイズのサブオブジェクトでない限り、そのオブジェクトのアドレスは、それが占める最初のバイトのアドレスです。
オブジェクトは他のオブジェクトを含むことができ、この場合、含まれるオブジェクトは元のオブジェクトにネストされています。オブジェクトaが別のオブジェクトbにネストされているのは、以下のいずれかの条件が満たされる場合です。
- aはbのサブオブジェクトです。
- bはaの記憶域を提供します。
- オブジェクトcが存在し、aがcにネストされており、cがbにネストされている場合。
オブジェクトは以下のいずれかのオブジェクトである場合、潜在的に非一意なオブジェクトです。
- 文字列リテラルオブジェクト。
|
(C++11以降) |
- 潜在的に非一意なオブジェクトのサブオブジェクト。
寿命がオーバーラップする任意の2つの非ビットフィールドオブジェクトについて
- 以下のいずれかの条件が満たされる場合、それらは同じアドレスを持つことがあります。
- 一方のオブジェクトが他方のオブジェクトにネストされている。
- いずれかのオブジェクトがゼロサイズのサブオブジェクトであり、それらの型が類似ではない。
- 両方とも潜在的に非一意なオブジェクトである。
- それ以外の場合、それらは常に異なるアドレスを持ち、互いに重ならないバイトの記憶域を占有します。
// character literals are always unique static const char test1 = 'x'; static const char test2 = 'x'; const bool b = &test1 != &test2; // always true // the character 'x' accessed from “r”, “s” and “il” // may have the same address (i.e., these objects may share storage) static const char (&r) [] = "x"; static const char *s = "x"; static std::initializer_list<char> il = {'x'}; const bool b2 = r != il.begin(); // unspecified result const bool b3 = r != s; // unspecified result const bool b4 = il.begin() != &test1; // always true const bool b5 = r != &test1; // always 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以降)。オブジェクトの型のアライメント要件を満たさない記憶域にオブジェクトを作成しようとすると、未定義の動作になります。
クラスのすべての非静的メンバーのアライメント要件を満たすために、一部のメンバーの後にパディングビットが挿入されることがあります。
#include <iostream> // objects of type S can be allocated at any address // because both S.a and S.b can be allocated at any address struct S { char a; // size: 1, alignment: 1 char b; // size: 1, alignment: 1 }; // size: 2, alignment: 1 // objects of type X must be allocated at 4-byte boundaries // because X.n must be allocated at 4-byte boundaries // because int's alignment requirement is (usually) 4 struct X { int n; // size: 4, alignment: 4 char c; // size: 1, alignment: 1 // three bytes of padding bits }; // size: 8, alignment: 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に等しいです。任意の型の最大の基本アライメントは実装定義です(C++11以降)であり、std::max_align_tのアライメントに等しいです(C++11以降)。
基本アライメントは、あらゆる種類の記憶域期間のオブジェクトでサポートされます。
|
型の配置が Allocator型は、オーバーアラインされた型を正しく処理する必要があります。 |
(C++11以降) |
|
new式と(C++17まで) std::get_temporary_bufferがオーバーアラインされた型をサポートするかどうかは実装定義です。 |
(C++11以降) (C++20まで) |
[編集] 備考
C++におけるオブジェクトは、オブジェクト指向プログラミング(OOP)におけるオブジェクトとは異なる意味を持ちます。
| C++におけるオブジェクト | OOPにおけるオブジェクト |
|---|---|
| 任意のオブジェクト型を持つことができる (std::is_objectを参照) |
クラス型でなければならない |
| 「インスタンス」の概念がない | 「インスタンス」の概念がある(そして、「インスタンスである」関係を検出するためのinstanceofのようなメカニズムがある) |
| 「インターフェース」の概念がない | 「インターフェース」の概念がある(そして、インターフェースが実装されているかどうかを検出するためのinstanceofのようなメカニズムがある) |
| 多態性は仮想メンバーを介して明示的に有効にする必要がある | 多態性は常に有効 |
欠陥レポートP0593R6では、定数評価中にバイト配列を作成したり、アロケーション関数(ユーザー定義およびconstexprである可能性のある)を呼び出したりする際に、暗黙のオブジェクト作成が発生すると考えられていました。しかし、そのような許可は定数評価における非決定性をもたらし、それは望ましくなく、一部の側面では実装不可能でした。その結果、P2747R2は定数評価におけるそのような暗黙のオブジェクト作成を禁止しました。私たちは、この変更全体が欠陥レポートではないにもかかわらず、意図的にこれを欠陥レポートとして扱います。
[編集] 欠陥レポート
以下の動作変更を伴う欠陥報告が、以前に公開された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の有効型と互換性がなかった |
暗黙のオブジェクト作成が追加された |
[編集] 関連項目
| C言語ドキュメントのオブジェクト
|