命名空间
变体
操作

对象

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

C++ 程序创建、销毁、引用、访问和操作对象

C++ 中的对象具有

以下实体不是对象:值、引用、函数、枚举器、类型、非静态类成员、模板、类或函数模板特化、命名空间、参数包和 this

变量是一个对象或引用,它不是非静态数据成员,并通过声明引入。

目录

[编辑] 对象创建

对象可以通过定义new 表达式throw 表达式、更改联合体的活跃成员以及评估需要临时对象的表达式来显式创建。在显式对象创建中,创建的对象是唯一确定的。

隐式生命周期类型的对象也可以通过以下方式隐式创建:

  • 除了常量求值期间,开始类型为 unsigned char 的数组生命周期的操作std::byte(C++17 起),在这种情况下,此类对象会在数组中创建,
  • 调用以下分配函数,在这种情况下,此类对象会在分配的存储中创建
(C++17 起)
  • 调用以下对象表示复制函数,在这种情况下,此类对象会在目标存储区域或结果中创建
(C++20 起)
  • 调用以下特定函数,在这种情况下,此类对象会在指定的存储区域中创建
  • std::start_lifetime_as
  • 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 由类型为 T 的非位域完整对象占用的 Nunsigned char 对象的序列,其中 Nsizeof(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
}

对于类型为 charsigned charunsigned char 的对象(除非它们是超大位域),其对象表示的每个位都必须参与值表示,并且每个可能的位模式都表示一个不同的值(不允许填充位、陷阱位或多种表示)。

[编辑] 子对象

对象可以有子对象。这包括

  • 成员对象
  • 基类子对象
  • 数组元素

不是其他对象子对象的对象称为完整对象

如果一个完整对象、成员子对象或数组元素是类类型,则其类型被认为是最派生类,以区别于任何基类子对象的类类型。最派生类类型或非类类型的对象称为最派生对象

对于一个类,

被称为其潜在构造子对象

[编辑] 大小

如果一个子对象是基类子对象或者使用 [[no_unique_address]] 属性声明的非静态数据成员(C++20 起),则它是潜在重叠子对象

对象 obj 只有在满足以下所有条件时才可能具有零大小

  • obj 是一个潜在重叠子对象。
  • obj 是一个没有虚成员函数和虚基类的类类型。
  • obj 不包含任何非零大小的子对象或非零长度的未命名位域

对于满足上述所有条件的对象 obj

  • 如果 obj标准布局(C++11 起)类类型且无非静态数据成员的基类子对象,则其大小为零。
  • 否则,在何种情况下 obj 具有零大小,这是实现定义的。

有关详细信息,请参见空基类优化

任何非位域、非零大小的对象必须占用一个或多个存储字节,包括由其任何子对象(全部或部分)占用的每个字节。如果对象是可平凡复制或标准布局(C++11 起)类型,则其占用的存储必须是连续的。

[编辑] 地址

除非对象是位域或零大小的子对象,否则该对象的地址是其占用的第一个字节的地址。

一个对象可以包含其他对象,在这种情况下,包含的对象嵌套在前一个对象中。如果满足以下任何条件,则对象 a 嵌套在另一个对象 b 中:

  • ab 的子对象。
  • ba 提供存储
  • 存在一个对象 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_casttypeid)使用,以便在运行时确定创建对象时所使用的类型,无论其在表达式中如何使用。

对于非多态对象,值的解释由对象在表达式中的使用方式决定,并在编译时确定。

#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 的幂),表示此类型对象可以分配的连续地址之间的字节数。

类型的对齐要求可以使用 alignofstd::alignment_of 查询。指针对齐函数 std::align 可用于在某个缓冲区内获取合适对齐的指针。std::aligned_storage 可用于获取合适对齐的存储。(C++23 前)

(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

最弱对齐(最小对齐要求)是 charsigned charunsigned char 的对齐,其等于 1;任何类型的最大基本对齐是实现定义的并且等于 std::max_align_t 的对齐(C++11 起)

基本对齐支持各种存储期的对象。

如果使用 alignas 使类型的对齐比 std::max_align_t 更严格(更大),则称其为具有扩展对齐要求的类型。对齐扩展的类型或其非静态数据成员具有扩展对齐的类类型是过度对齐类型

分配器类型需要正确处理过度对齐类型。

(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 文档 关于 对象