对象
C++ 程序创建、销毁、引用、访问及操作 对象。
C++ 中的对象具有:
sizeof可确定的- 大小;
alignof可确定的 - 对齐要求;存储期
- (自动、静态、动态、线程局部);生存期
- (由存储期或临时量界定);类型
- ;值 (可能未确定,例如 默认初始化
- 的非类类型);可选地,一个 名字
。
this下列实体不是对象:值、引用、函数、枚举项、类型、非静态类成员、模板、类或函数模板特化、命名空间、参数包 和
。 变量 是经 声明
参阅
对象创建可通过 定义new、 表达式throw、 表达式、更改 联合 的活跃成员以及求值需要 临时对象
的表达式显式创建对象。在显式对象创建中,被创建的对象唯一定义。 隐式生存期类型
- 的对象也可通过以下方式隐式创建:
unsigned char除常量求值外,开始类型为 或 std::byte(C++17 起) - 的数组生存期的操作,在此情况下,此类对象在数组中创建;
- 对以下分配函数的调用,在此情况下,此类对象在分配的存储中创建:operator new
- (除常量求值外)operator new[]
- (除常量求值外)
- std::malloc
- std::calloc
|
std::aligned_alloc |
- (C++17 起)对以下 对象表示
- 复制函数的调用,在此情况下,此类对象在存储的目标区域或结果中创建:
- std::memcpy
| std::bit_cast |
|
std::start_lifetime_as_array |
(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
|
完整对象类型 N unsigned char类型 的非位域T 完整对象所占用的 N 对象序列,其中 sizeof(T)
|
为 T类型 T
|
的对象表示中参与表示类型 obj 的值的位集合T
|
类型 obj 的非位域完整对象 T
|
obj 中对应于 T
|
的对象表示的字节bf
|
N 中对应于 bf 的值表示的位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位域char8_t 大小等。
对于类型为 、、
、
(C++20 起) 的对象(除非它们是超大的 位域
- ),要求对象表示的每一位都参与值表示,并且每个可能的位模式都表示一个不同的值(不允许填充位、陷阱位或多个表示)。
- 子对象
- 对象可以有
子对象。包括: 成员对象
基类子对象数组元素不是其他对象的子对象的对象称为 完整对象 。 如果完整对象、成员子对象或数组元素是 类类型
,则其类型被视为
- 最终派生类,以区别于任何基类子对象的类类型。最终派生类类型或非类类型的对象称为 最终派生对象
- 。 对于类, 其非静态
- 数据成员,其非虚直接 基类
,并且,如果该类不是 抽象
类,其
虚基类被称为其 潜在构造的子对象。
[[no_unique_address]]大小如果子对象是基类子对象 或声明有
属性obj(C++20 起)
obj的非静态数据成员,则它是obj潜在重叠的子对象obj。 对象 只有在满足以下所有条件时才可能具有零大小:
obj 是潜在重叠的子对象。
: 如果 是
标准布局(C++11 起) 类类型且没有非静态数据成员的基类子对象,则它具有零大小。
否则,
在何种情况下具有零大小由实现定义。详见 空基类优化。 任何大小非零的非位域对象必须占用一个或多个字节的存储,包括由其任何子对象(完全或部分)占用的每个字节。如果对象是可平凡复制
或标准布局(C++11 起) 类型,则占用的存储必须是连续的。
a地址b除非对象是位域或大小为零的子对象,否则该对象的
a地址b是其占用的第一个b字节 的地址。a对象可以包含其他对象,此时被包含的对象- 嵌套在
c前者对象内。如果满足以下任一条件,则对象a嵌套在另一个对象c内:cb是
的子对象。 为
- 提供存储。存在对象
|
嵌套在 |
|
字符串字面量 |
- 对象。
初始化列表的 后备数组。
- (C++11 起)
- 数组类型的模板参数对象(参见
- std::meta::reflect_constant_string 和 std::meta::reflect_constant_array
- )。
- (C++26 起)
// 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
潜在非唯一对象的子对象。
对于任何两个具有重叠 生存期 的非位域对象:
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多态对象
声明或继承至少一个虚函数的类类型的对象是多态对象。在每个多态对象中,实现存储额外的信息(在所有现有实现中,除非优化掉,否则是一个指针),这些信息由
虚函数 调用和 RTTI 特性( 和 )使用,以在运行时确定创建对象时使用的类型,而与其用于的表达式的类型无关。 对于非多态对象,值的解释由使用该对象的表达式确定,并在编译时决定。 运行此代码可能的输出:
|
严格别名使用 |
std::size_t |
,且始终为 2 的幂),表示此类类型的对象可以分配到的连续地址之间的字节数。
类型的对齐要求可通过 alignas 或 std::alignment_of
查询。指针对齐函数 std::align 可用于在某个缓冲区中获得适当对齐的指针。std::aligned_storage 可用于获得适当对齐的存储。
#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';
}
(C++11 起)
alignof(S) = 1
sizeof(S) = 2
alignof(X) = 4
sizeof(X) = 8
每个对象类型将其对齐要求强加于该类型的每个对象char;可以使用 signed char(C++11 起)unsigned char 请求更严格的对齐(具有更大的对齐要求)。尝试在不满足对象类型对齐要求的存储中创建对象是未定义行为。
1为了满足 类 的所有非静态成员的对齐要求,可能会在其某些成员之后插入 填充位。
运行此代码可能的输出:
最弱对齐(最小对齐要求)是
|
、 和 的对齐值,等于 ;标量类型最大的 基础对齐值 由实现定义,等于 std::max_align_t(C++11 起) |
的对齐值。 |
|
如果实现不支持在特定上下文中请求特定的扩展对齐,则程序是非良构的。
|
过度对齐类型 。 |
分配器
类型需要正确处理过度对齐类型。 (C++11 起)
| 表达式 | 和 |
|---|---|
| (C++17 前) std::get_temporary_buffer 是否支持过度对齐类型由实现定义。 (C++11 起) |
(C++20 前) |
| 注解 | C++ 中的对象与 instanceof面向对象编程(OOP) |
| 中的对象含义不同: | C++ 中的对象
instanceofOOP 中的对象
|
| 可以具有任何对象类型 | (参见 |
std::is_object)
必须具有类类型
没有“实例”的概念
有“实例”的概念(并且有诸如 constexpr 的机制来检测“instance-of”关系)
没有“接口”的概念
有“接口”的概念(并且有诸如
的机制来检测接口是否已实现)
多态需要通过虚成员显式启用
| 多态始终启用 | 在缺陷报告 | P0593R6 | 中,隐式对象创建被认为发生在创建字节数组或调用 |
|---|---|---|---|
| 分配函数 | (可能是用户定义的并且 | )时,但在常量求值期间除外。然而,这种允许导致了常量求值中的非确定性,这是不可取的并且在某些方面无法实现。因此, | P2747R2 |
| 禁止了常量求值中的这种隐式对象创建。尽管整个论文不全是缺陷报告,但我们有意将此更改视为缺陷报告。 | 缺陷报告 | 以下更改行为的缺陷报告已追溯应用于先前发布的 C++ 标准。
DR 应用于 |
发布时的行为
正确行为 CWG 633 |
| C++98 | 变量只能是对象 | 它们也可以是引用
CWG 734 |
C++98
在同一作用域中定义、保证具有相同值的变量是否可以有相同地址是未指定的 |
| 如果它们的生存期重叠,则保证地址不同, | 与它们的值无关 | CWG 1189 C++98 同一类型的两个基类子对象可以有相同的地址 |
它们始终具有不同的地址 |
| CWG 1861 | C++98 | char[]对于窄字符类型的超长位域,对象表示的所有位仍然参与值表示
允许填充位 |
CWG 2489 C++98 char[]
|
| 不能提供存储,但可以在其存储内部隐式创建对象 | 不能在 | 的存储内部隐式创建对象 | |
| CWG 2519 | C++98 | 对象表示的定义没有涉及位域
涉及位域 |
CWG 2719 C++98 |
| 在未对齐存储中创建对象的行为不清楚 | 此情况下行为未定义 | CWG 2753 C++11 |
初始化列表的后备数组是否可以与字符串字面量共享存储不清楚 |
| 它们可以共享存储 | CWG 2795 | C++98
在确定两个具有重叠生存期的对象是否可以具有相同地址时,如果其中任一个是大小为零的子对象,则它们可能具有相似的独特类型 仅允许不相似的类型 |
P0593R6 |
| C++98 | 先前的对象模型不支持标准库所需的许多有用惯用法,并且与 C 中的有效类型不兼容 | 添加了隐式对象创建
参阅 C 文档 |
关于 |