“cpp/language/object”的版本间的差异
小 |
|||
第73行: | 第73行: | ||
|- | |- | ||
|完整对象类型 {{tt|T}} | |完整对象类型 {{tt|T}} | ||
− | |一个 {{tt|T}} 类型的非{{rlp|bit field|位域}}完整对象占据的 {{c|N}} 个 {{c/core|unsigned char}} 对象 | + | |一个 {{tt|T}} 类型的非{{rlp|bit field|位域}}完整对象占据的 {{c|N}} 个 {{c/core|unsigned char}} 对象,其中 {{c|N}} 是 {{c|sizeof(T)}} |
|{{tt|T}} 的对象表示中有参与表示 {{tt|T}} 的值的所有位的集合 | |{{tt|T}} 的对象表示中有参与表示 {{tt|T}} 的值的所有位的集合 | ||
|- | |- |
2025年3月29日 (六) 22:29的最后版本
C++ 程序可以创建、销毁、引用、访问并操作对象。
在 C++ 中,一个对象拥有这些性质:
- 大小(可以使用
sizeof
获取); - 对齐要求(可以使用
alignof
获取); - 存储期(自动、静态、动态、线程局部);
- 生存期(与存储期绑定或者为临时)
- 类型;
- 值(可能是不确定的,例如默认初始化的非类类型);
- 一个可选的名字。
以下实体都不是对象:值,引用,函数,枚举项,类型,类的非静态成员,模板,类或函数模板的特化,命名空间,形参包,和 this。
变量 由声明引入,是对象或者是并非非静态数据成员的引用。
目录 |
[编辑] 对象创建
对象可以使用定义、new 表达式、throw 表达式、更改联合体的活跃成员和求值要求临时对象的表达式显式创建。显式对象创建完全定义了所创建的对象。
隐式生存期类型的对象也可以由下列操作隐式创建:
- 在常量求值以外的场合,开始类型 unsigned char 或 std::byte(C++17 起) 的数组生存期的操作,此时在该数组中创建这种对象,
- 调用下列分配函数,此时在分配的存储中创建这种对象:
- operator new (在常量求值以外的场合)
- operator new[] (在常量求值以外的场合)
- std::calloc
- std::malloc
- std::realloc
(C++17 起) |
- 调用下列对象表示复制函数,此时在目标存储区域或结果中创建这种对象:
(C++20 起) |
|
(C++23 起) |
同一存储区域中可以创建零或多个对象,只要能给予程序有定义的行为即可。如果无法这样创建,例如操作冲突,那么程序行为未定义。如果多个这种隐式创建的对象的集合会给予程序有定义行为,那么不指定这些集合中的哪一个被创建。换言之,不要求隐式创建的对象是唯一定义的。
在指定的存储区域内隐式创建对象后,一些操作会生成指向已适当创建的对象 的指针。已适当创建的对象与存储区域拥有相同地址。类似地,当且仅当不存在能给予程序有定义行为的指针值时,行为才未定义;而如果有多个给予程序有定义行为的值,那么不指定产生哪个值。
#include <cstdlib> struct X { int a, b; }; X* MakeX() { // 可能的有定义行为之一: // 调用 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,或者联合体类型的隐式定义的复制/移动特殊成员函数,也能创建对象。
[编辑] 对象表示与值表示
某些类型和对象具有对象表示 和值表示,它们在下表中定义:
实体 | 对象表示 | 值表示 |
---|---|---|
完整对象类型 T
|
一个 T 类型的非位域完整对象占据的 N 个 unsigned char 对象,其中 N 是 sizeof(T)
|
T 的对象表示中有参与表示 T 的值的所有位的集合
|
T 类型的非位域完整对象 obj
|
obj 中对应 T 的对象表示的字节
|
obj 中对应 T 的值表示的位
|
位域对象 bf | bf 占据的 N 个位的序列,其中 N 是该位域的宽度 | bf 的对象表示中有参与表示 bf 的值的所有位的集合 |
类型或对象的对象表示中不属于值表示的位是填充位。
对于可平凡复制类型,它的值表示是对象表示的一部分,这意味着复制该对象在存储中所占据的字节就足以产生另一个具有相同值的对象(除非这个值是该类型的一个“陷阱表示”,将它读取到 CPU 中会产生一个硬件异常,就像浮点数的 SNaN(“Signaling NaN 发信非数”)或整数的 NaT(“Not a Thing 非事物”)。
尽管大多数实现都不允许整数的陷阱表示、填充位或多重表示,也还存在例外;例如 Itanium 上的整数类型值就可以是陷阱表示。
反过来不一定是对的:可平凡复制类型的两个具有不同对象表示的对象可能表现出相同的值。例如,浮点数有多种位模式都表示相同的特殊值 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() { static_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 类型的对象,除非它们是大小过大的位域,否则它的对象表示的每个位都参与它的值表示,而且每一种位模式都表示一个独立的值(没有填充位或陷阱位,不允许值的多种表示)。