【C++对象模型】构造函数III

构造函数语意学

》》构造函数语意学I---默认构造函数的构造操作《《

》》构造函数语意学II---拷贝构造函数的构造操作《《

》》构造函数语意学III---程序转化语意学《《

程序转化语意学

显式的初始化操作

有这样的定义

cpp 复制代码
X x0;
X x1(x0);
X x2 = x0;
X x3 = X(x0);

必要的程序转化有两个阶段:

  1. 重写每一个定义,其中的初始化操作会被剥除。
  2. class的拷贝构造调用操作会被安插进去。

参数的初始化

C++标准(Section 8.5)说,把一个class object当做参数传给一个函数(或是作为一个函数的返回值),相当于以下形式的初始化操作

cpp 复制代码
X xx = arg;

其中xx代表形式参数(或返回值)而arg代表真正的参数值。

若已知这个函数以及下面这样的调用方式

cpp 复制代码
void foo(X x0);
X xx;
foo(xx);

在编译器实现技术上,有一种策略是导入所谓的临时性 object,并调用拷贝构造将它初始化,然后将此临时性object交给函数。例如将前一段程序代码转换如下:

cpp 复制代码
X __temp0;
__temp0.X::X(xx);
foo(__temp0);

临时性object先以class X的拷贝构造正确地设定了初值,然后再以bitwise方式拷贝到x0这个局部实例中。噢,真讨厌,foo()的声明因而也必须被转化,形式参数必须从原先的一个X对象改变为一个X引用,像这样:

cpp 复制代码
void foo(X&x0);
其中class X声明了一个析构函数,它会在foo()函数完成之后被调用,对付那个临时性的object。

另一种实现方法是以"拷贝建构"(copy construct)的方式把实际参数直接建构在其应该的位置上,此位置视函数活动范围的不同,记录于程序堆栈中。

返回值的初始化

cpp 复制代码
X bar(){
    X xx;
    ...
    return xx;
}

bar()的返回值如何从局部对象xx中拷贝过来

  1. 首先加上一个额外参数,类型是 class object的一个引用。
  2. 在 return指令之前安插一个拷贝构造调用操作,以便将欲传回之object的内容当做上述新增参数的初值。

现在编译器必须转换每一个bar()调用操作,以反映其新定义。

cpp 复制代码
X xx = bar();
    |
    |
    V
X xx;
bar(xx);

在编译器层面做优化

在一个像bar()这样的函数中,因此编译器有可能自己做优化,方法是以result参数取代named return value。

cpp 复制代码
void bar(X &__result){
    //构造函数被调用
    __result.X::X();
    ...//直接处理__result
    return;
}

这样的编译器优化操作,有时候被称为Named Return Value(NRV)优化。NRV优化如今被视为标准C++编译器的一个义不容辞的优化操作------虽然其需求其实超越了正式标准之外。

有的个程序的第不能实施 NRV 优化,因为 test class 缺少一个拷贝构造。

虽然NRV优化提供了重要的效率改善,但它还是饱受批评。

想象你已经摆好了你的拷贝构造的阵势,使你的程序"以copying方式产生出一个object时",对称地调用析构

cpp 复制代码
void foo{
    //这里希望有个拷贝构造
    X xx = bar();
    ...
    //这里调用析构
}

在此情况下,对称性被优化给打破了;程序虽然比较快,却是错误的。

Copy Constructor:要还是不要?

cpp 复制代码
class Point3d{
public:
    point3d(float x , float y , float z);
private:
    float _x, _y, _z;
}

这个class的设计者应该提供一个显式拷贝构造吗?

答案当然很明显是no。没有任何理由要你提供一个拷贝构造函数实例,因为编译器自动为你实施了最好的行为。比较难以回答的是,如果你被问及是否预见class需要大量的memberwise初始化操作,例如以传值(by value)的方式传回objects?如果答案是yes,那么提供一个构造的显式inline函数实例就非常合理------在"你的编译器提供NRV优化"的前提下。

成员们的初始化队伍(成员初始化列表)

在下列情况下,为了让你的程序能够被顺利编译,你必须使用成员初始化列表

  1. 当初始化一个 reference成员时;
  2. 当初始化一个 const成员时;
  3. 当调用一个 base class的构造,而它拥有一组参数时;
  4. 当调用一个 member class的构造,而它拥有一组参数时。
  • list内部的真正操作是什么

编译器会一一操作初始化列表,以适当顺序在构造之内安插初始化操作,并且在任何显示用户代码之前。

事实上,有一些微妙的地方要注意:list中的项目顺序是由class中的members声明顺序决定的,不是由初始化列表中的排列顺序决定的。


相关推荐
小字节,大梦想17 分钟前
【C++】二叉搜索树
数据结构·c++
吾名招财19 分钟前
yolov5-7.0模型DNN加载函数及参数详解(重要)
c++·人工智能·yolo·dnn
XKSYA(小巢校长)34 分钟前
NatGo我的世界联机篇
开发语言·php
Cons.W37 分钟前
Codeforces Round 975 (Div. 1) C. Tree Pruning
c语言·开发语言·剪枝
我是哈哈hh38 分钟前
专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
服务器·数据结构·c++·算法·机器学习·深度优先·剪枝
憧憬成为原神糕手39 分钟前
c++_ 多态
开发语言·c++
VBA633740 分钟前
VBA信息获取与处理第三个专题第三节:工作薄在空闲后自动关闭
开发语言
郭二哈42 分钟前
C++——模板进阶、继承
java·服务器·c++
挥剑决浮云 -1 小时前
Linux 之 安装软件、GCC编译器、Linux 操作系统基础
linux·服务器·c语言·c++·经验分享·笔记
丶Darling.1 小时前
LeetCode Hot100 | Day1 | 二叉树:二叉树的直径
数据结构·c++·学习·算法·leetcode·二叉树