物件
C++ 程式建立、銷燬、引用、訪問和操作物件。
C++ 中的物件具有
- 大小(可用
sizeof
確定); - 對齊要求(可用
alignof
確定); - 儲存期(自動、靜態、動態、執行緒區域性);
- 生命期(受儲存期或臨時物件限制);
- 型別;
- 值(可能不確定,例如對於預設初始化的非類型別);
- 可選的名稱。
以下實體不是物件:值、引用、函式、列舉器、型別、非靜態類成員、模板、類或函式模板特化、名稱空間、引數包和 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 |
obj 中對應於 T 物件表示的位元組 |
obj 中對應於 T 值表示的位元位 |
位域物件 bf | bf 佔用的 N 位元位序列,其中 N 是位域的寬度 | bf 的物件表示中參與表示 bf 值的位元位集合 |
型別或物件的物件表示中不屬於值表示的位元位是填充位元位。
對於可平凡複製型別,值表示是物件表示的一部分,這意味著複製物件在儲存中佔用的位元組足以產生另一個具有相同值的物件(除非物件是潛在重疊子物件,或者該值是其型別的陷阱表示,並且將其載入到 CPU 會引發硬體異常,例如 SNaN(“發出訊號的非數字”)浮點值或 NaT(“非事物”)整數)。
儘管大多數實現不允許整數型別的陷阱表示、填充位或多種表示,但也有例外;例如,Itanium 上的整數型別值可能是陷阱表示。
反之不一定成立:兩個可平凡複製型別的物件,即使物件表示不同,也可能表示相同的值。例如,多個浮點位模式表示相同的特殊值 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 具有零大小,這是實現定義的。
有關詳細資訊,請參見空基類最佳化。
任何非位域、非零大小的物件必須佔用一個或多個儲存位元組,包括由其任何子物件(全部或部分)佔用的每個位元組。如果物件是可平凡複製或標準佈局(C++11 起)型別,則其佔用的儲存必須是連續的。
[編輯] 地址
除非物件是位域或零大小的子物件,否則該物件的地址是其佔用的第一個位元組的地址。
一個物件可以包含其他物件,在這種情況下,包含的物件巢狀在前一個物件中。如果滿足以下任何條件,則物件 a 巢狀在另一個物件 b 中:
- a 是 b 的子物件。
- b 為 a 提供儲存。
- 存在一個物件 c,其中 a 巢狀在 c 中,且 c 巢狀在 b 中。
如果一個物件是以下物件之一,則它是潛在非唯一物件
- 字串字面量物件。
|
(C++11 起) |
- 潛在非唯一物件的子物件。
對於任何兩個生命週期重疊的非位域物件
- 如果滿足以下任何條件,它們可能具有相同的地址
- 其中一個巢狀在另一箇中。
- 其中任何一個都是零大小的子物件,並且它們的型別不相似。
- 它們都是潛在非唯一物件。
- 否則,它們總是具有不同的地址並佔用不相交的儲存位元組。
// 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
[編輯] 多型物件
宣告或繼承至少一個虛擬函式的類型別的物件是多型物件。在每個多型物件中,實現儲存額外的資訊(在每個現有實現中,它是一個指標,除非被最佳化掉),這些資訊被虛擬函式呼叫和 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;任何型別的最大基本對齊是實現定義的並且等於 std::max_align_t 的對齊(C++11 起)。
基本對齊支援各種儲存期的物件。
如果使用 分配器型別需要正確處理過度對齊型別。 |
(C++11 起) |
new 表示式和(C++17 前) std::get_temporary_buffer 是否支援過度對齊型別是實現定義的。 |
(C++11 起) (C++20 前) |
[編輯] 注意
C++ 中的物件與面向物件程式設計 (OOP) 中的物件具有不同的含義
C++ 中的物件 | OOP 中的物件 |
---|---|
可以是任何物件型別 (見 std::is_object) |
必須是類型別 |
沒有“例項”概念 | 有“例項”概念(並且有 instanceof 等機制來檢測“例項-of”關係) |
沒有“介面”概念 | 有“介面”概念(並且有 instanceof 等機制來檢測是否實現了介面) |
多型性需要透過虛成員顯式啟用 | 多型性總是啟用的 |
在缺陷報告 P0593R6 中,隱式物件建立被認為發生在建立位元組陣列或在常量求值期間呼叫分配函式(可能是使用者定義的和 constexpr
)時。然而,這種允許導致常量求值中的不確定性,這是不希望的,並且在某些方面是不可實現的。因此,P2747R2 禁止在常量求值中進行此類隱式物件建立。我們有意將此更改視為缺陷報告,儘管整個論文並非如此。
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 633 | C++98 | 變數只能是物件 | 它們也可以是引用 |
CWG 734 | C++98 | 未指定在同一作用域內 保證具有相同值的變數 是否可以具有相同的地址 |
如果它們的生命週期重疊, 則保證地址不同, 無論它們的值如何 |
CWG 1189 | C++98 | 兩個相同型別的基類子物件 可以具有相同的地址 |
它們總是具有 不同的地址 |
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 | 當確定兩個生命週期重疊的物件 是否可以具有相同地址時,如果其中任何一個 是零大小的子物件,它們可以具有相似但不同的型別 |
只允許非相似型別 |
P0593R6 | C++98 | 以前的物件模型不支援許多 標準庫所需的有用習語 且與 C 中的有效型別不相容 |
添加了隱式物件建立 |
[編輯] 另請參閱
C 文件 關於 物件
|