6.7.2 Object model
6.7.6 Alignment
1 | struct B { long double d; }; |
Paw5zx个人理解:点击查看更多
对齐值越大,对应的要求就越严格
1 | std::cout << std::alignment_of<char>::value << std::endl; // 1 |
如果一个地址能满足某个更严格的对齐要求,它也必然满足所有较弱的对齐要求:
- 地址
0x08是8的倍数 → 它可以放double,也可以放int和char - 地址
0x04是4的倍数 → 它可以放int和char,但不能放double - 地址
0x01是1的倍数 → 它可以放char,但不能放double和int
完整类型的对齐要求可以通过alignof表达式来查询。此外,窄字符类型(narrow character types)必须具有最弱的对齐要求。【注释:这使得普通字符类型能够作为底层类型来管理一个具备对齐要求的内存区域(见[dcl.align])。——注释结束】
对齐量之间的比较是有意义的,并且会得到显而易见的结果:
- (7.1) 当两个对齐量的数值相等时,它们相等。
- (7.2) 当两个对齐量的数值不相等时,它们不等。
- (7.3) 当一个对齐量大于另一个时,它表示更严格的对齐。
6.7.7 Temporary objects
临时对象在下列情况下被创建:
- (1.1) 当纯右值被转换为将亡值时([conv.rval]);
- (1.2) 当实现需要传递或返回平凡可复制类型的对象时(见下文);以及
- (1.3) 当抛出异常时([except.throw])。【注释:异常对象的生存期在[[except.throw]]中描述。——注释结束】
即使临时对象的创建处于未求值语境([expr.prop]),所有语义限制仍应得到遵守,就如同该临时对象已被创建并随后被销毁了一样(编译阶段假装他会执行,并据此检查所有相关规则)。
【注释:语义限制包括所选构造函数和析构函数的可访问性([class.access])及是否被删除。然而,在decltype说明符([expr.call])操作数这种特殊情形中,不会引入临时a对象,因此上述规定不适用于此类纯右值。——注释结束】
Paw5zx个人理解:点击查看更多
即使临时对象的创建处于未求值语境,所有语义限制仍应得到遵守,就如同该临时对象已被创建并随后被销毁了一样。
1 | class A |
然而,在decltype说明符操作数这种特殊情形中,不会引入临时对象,因此上述规定不适用于此类纯右值。
1 |
临时对象的物化通常被尽可能延迟,以避免创建不必要的临时对象。【注释:临时对象在下列情况下被物化:
- (2.1) 将引用绑定到纯右值时([dcl.init.ref]、[expr.type.conv]、[expr.dynamic.cast]、[expr.static.cast]、[expr.const.cast]、[expr.cast]),
- (2.2) 对类类型的纯右值进行成员访问时([expr.ref]、[expr.mptr.oper]),
- (2.3) 对数组纯右值进行数组到指针转换或下标操作时([conv.array]、[expr.sub]),
- (2.4) 从花括号初始化列表初始化
std::initializer_list<T>类型对象时([dcl.init.list]), - (2.5) 对于某些未求值操作数([expr.typeid]、[expr.sizeof]),以及
- (2.6) 当非cv void类型的纯右值作为弃值表达式(
discarded-value expression)出现时([expr.prop])。
——注释结束】【示例:
考虑以下代码:
1 | class X |
X(2)直接在f()参数的空间中被构造,Y(3)直接在g()参数的空间中被构造。同样,f()的结果直接构造在b中,g()的结果直接构造在c中。另一方面,表达式a = f(a)需要为f(a)的结果创建一个临时对象,该临时对象被物化以便X::operator=(const X&)的引用参数可以绑定到它。——示例结束】
Paw5zx个人理解(针对2.1):点击查看更多
将引用绑定到纯右值时
1 | struct X |
Paw5zx个人理解(针对2.2):点击查看更多
对类类型的纯右值进行成员访问时
1 | void test_2_2() |
Paw5zx个人理解(针对2.3):点击查看更多
1 |
Paw5zx个人理解(针对2.4):点击查看更多
1 |
Paw5zx个人理解(针对2.5):点击查看更多
1 |
Paw5zx个人理解(针对2.6):点击查看更多
1 |
Paw5zx个人理解:点击查看更多
X(2)直接在f()参数的空间中被构造
自C++17起,X(2)不会先创建一个临时对象再拷贝给参数,而是直接在参数对象的存储空间中构造。(copy elision)
f()的结果直接构造在b中
同样返回值也一样被优化,返回值直接构造在变量b的存储空间中
表达式
a = f(a)需要为f(a)的结果创建一个临时对象,该临时对象被物化以便X::operator=(const X&)的引用参数可以绑定到它。
【【以下未完成分析,缺少知识点:copy elision,更详细的是NRVO以及延申知识点:临时物化,临时对象(又回来了),主要弄清,临时对象是个什么状态以及是否真的在内存中创建(构造)这么个对象。所以先完成余下部分的学习
对于a = f(a),情况有些不同,为了演示,我们补全class X,f(X)和g(Y):
1 | class X |
现在对于:
1 | X a(1); |
- ①首先构造a,调用的是
X(int) - ②然后是
f(X)的实参传递,由于a是左值,按值传递,所以调用一次拷贝构造,把a拷贝至param - ③现在进入
f(X),用param拷贝构造local - ④在
f(X)的返回语句中,NRVO生效,因此没有从local到返回对象的构造,而是直接 由于axxx,所以将local赋值给a,调用拷贝赋值 - ⑤
f(X)结束,析构临时对象local以及形参param,析构顺序这里不讨论 - ⑥析构
a
所以输出为:
1 | X(int) ctor |
当类类型X的对象被传递给函数或从函数返回时,如果X至少有一个可用的(eligible)拷贝构造函数或移动构造函数([special]),且每个这样的构造函数都是平凡的,并且X的析构函数要么是平凡的要么是被删除的,则允许实现创建一个临时对象用来保存函数形参或结果对象。
该临时对象从函数实参(或返回值表达式)构造,然后函数形参(或返回对象)被初始化,就好像是通过一个可用的平凡的构造函数,从该临时对象拷贝初始化而来一样。(即使该构造函数不可访问,或者不会被重载决议选择来执行对象的复制或移动)。【注释:这一自由度旨在允许在寄存器中传递或返回类类型的对象。——注释结束】
Paw5zx个人理解:点击查看更多
允许(非强制)编译器把满足一定条件的“简单类对象”放在寄存器中传递或返回,而不是一定要真正在栈上放一个完整的对象再调用拷贝构造,从而提高效率。
编译器只有在满足以下条件时才可以做这种特殊处理:
- X有至少一个可用的拷贝或移动构造函数(即编译器认为可以用它来拷贝/移动对象)
- 每个这样的构造函数都是平凡的(即可以按位拷贝,不需要运行用户代码)
- X的析构函数要么是平凡的要么是被删除的(不需要运行用户代码)
临时对象的创建方式是:
- ①编译器先根据实参(或返回值表达式)创建一个临时对象
即使该构造函数是:xxxxx
编译器仍可以xxxx,行为就好像xxxx(但实际没有)