Object
C++程序创建、销毁、引用、访问和操作 对象 。
在C++中,对象具有
-
大小(可通过
sizeof确定); -
对齐要求(可通过
alignof确定); - 存储期 (自动、静态、动态、线程局部);
- 生存期 (受存储期或临时对象限制);
- 类型 ;
- 值(可能是不确定的,例如 默认初始化 的非类类型);
- 可选的 名称 。
以下实体不是对象:值、引用、函数、枚举项、类型、非静态类成员、模板、类或函数模板特化、命名空间、参数包以及 this 。
一个 变量 是通过 声明 引入的、非非静态数据成员的对象或引用。
目录对象创建对象可以通过 定义 、 new 表达式 、 throw 表达式 、更改 联合体 的活动成员以及需要创建 临时对象 的表达式求值来显式创建。在显式对象创建中,被创建的对象具有唯一性定义。 隐式生存期类型的对象也可以通过以下方式隐式创建:
只要能够使程序具有定义行为,就可以在同一存储区域创建零个或多个对象。如果这种创建无法实现(例如由于操作冲突),则程序的行为是未定义的。如果多组隐式创建的对象都能使程序具有定义行为,那么具体创建哪组对象是未指定的。换句话说,隐式创建的对象不需要被唯一定义。 在存储区域的指定范围内隐式创建对象后,某些操作会产生指向 适宜创建对象 的指针。该适宜创建对象的地址与存储区域地址相同。同样地,仅当不存在能使程序具有定义行为的指针值时,其行为才是未定义的;若存在多个能使程序具有定义行为的指针值,则未指定具体产生哪个指针值。 #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 的调用或 联合体 类型的隐式定义复制/移动特殊成员函数也可以创建对象。 对象表示与值表示某些类型和对象具有 对象表示 和 值表示 ,其定义如下表所示:
类型或对象在对象表示中不属于值表示部分的位称为 填充位 。 对于 可平凡复制 类型,值表示是对象表示的一部分,这意味着复制对象在存储中占用的字节就足以生成具有相同值的另一个对象(除非该对象是潜在重叠子对象,或者该值是其类型的 陷阱表示 且加载到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 的对象(除非它们是超尺寸的 位域 ),其对象表示的每一位都必须参与值表示,且每个可能的位模式都对应一个唯一的值(不允许存在填充位、陷阱位或多重表示)。 子对象一个对象可以拥有 子对象 。这些包括
不是其他对象的子对象的对象被称为 完整对象 。 如果一个完整对象、成员子对象或数组元素属于 类类型 ,其类型被视为 最终派生类 ,以区别于任何基类子对象的类类型。具有最终派生类类型或非类类型的对象被称为 最终派生对象 。 对于一个类, 被称为其 潜在构造子对象 。 大小
子对象在以下情况下是
潜在重叠子对象
:当它是基类子对象
,或是使用
一个对象 obj 仅当满足以下所有条件时才可能具有零大小:
对于满足上述所有条件的对象 obj :
有关更多详细信息,请参阅 空基类优化 。 任何具有非零大小的非位域对象必须占据一个或多个字节的存储空间,包括被其任何子对象(完全或部分)占用的每个字节。若对象为可平凡复制 或标准布局 (since C++11) 类型,则其占用的存储空间必须是连续的。 地址除非对象是位域或零大小子对象,否则该对象的 地址 是其占用的首个 字节 的地址。 一个对象可以包含其他对象,这种情况下被包含的对象被称为 嵌套在 前者对象中。当满足以下任一条件时,对象 a 即嵌套于另一个对象 b 中:
一个对象是 潜在非唯一对象 ,当它属于以下对象之一:
对于任意两个具有重叠 生存期 的非位域对象:
// 字符字面量始终具有唯一地址 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 多态对象
声明或继承至少一个虚函数的类类型对象是多态对象。在每个多态对象内部,实现会存储额外信息(在所有现有实现中,除非被优化掉,通常是一个指针),这些信息被
虚函数
调用以及RTTI特性(
对于非多态对象,值的解释由使用该对象的表达式决定,并在编译时确定。
运行此代码
#include <iostream> #include <typeinfo> struct Base1 { // 多态类型:声明了虚成员 virtual ~Base1() {} }; struct Derived1 : Base1 { // 多态类型:继承了虚成员 }; struct Base2 { // 非多态类型 }; struct Derived2 : Base2 { // 非多态类型 }; int main() { Derived1 obj1; // 使用 Derived1 类型创建的对象 obj1 Derived2 obj2; // 使用 Derived2 类型创建的对象 obj2 Base1& b1 = obj1; // b1 引用对象 obj1 Base2& b2 = obj2; // b2 引用对象 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 严格别名
使用与对象创建时类型不同的表达式访问该对象,在许多情况下属于未定义行为,有关例外情况和示例列表,请参阅
对齐每个对象类型都具有称为对齐要求的属性,它是一个非负整数值(类型为std::size_t,并且总是2的幂),表示该类型对象可以分配到的连续地址之间的字节数。
每个对象类型都对其每个对象施加其对齐要求;可以使用 为了满足类的所有非静态成员的对齐要求,可能会在其某些成员之后插入填充位。 运行此代码 #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
最弱的对齐(最小的对齐要求)是 如果实现不支持在特定上下文中对特定扩展对齐的请求,则程序为非良构。
注释C++ 中的对象与 面向对象编程(OOP) 中的对象具有不同含义:
在缺陷报告
P0593R6
中,曾考虑在常量求值期间创建字节数组或调用
分配函数
(可能是用户定义且为
缺陷报告以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
参见
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||