第二章:构造函数语义学:拷贝构造函数

  记录一下第二章的第二部分笔记

 

一、拷贝构造函数

  1.拷贝构造函数的生成:

  同默认构造函数一样,概念上编译器会在需要调用拷贝构造函数时生成一个合成的拷贝构造函数,但实际上仅为有意义nontrival的类生成,对于trivial的类则使用逐比特拷贝bitwise copies(我理解为简单的将成员变量复制,如果有类成员则递归复制即对成员类内进行逐比特拷贝,因为这些类的内存分布简单不需要做额外操作,按内存逐笔特复制即可(不需要调用其他类的拷贝构造函数或修改虚函数表))。

  对于拷贝构造函数trivial代表Bitwise Copy Semantics(逐比特拷贝)。有四种情况的类为有意义的nontrivial:①类成员变量包含拷贝构造函数(合成的或显式的)②类继承自含有拷贝构造函数(合成or显式)的基类③类含有虚函数④类包含有虚基类。注意上述合成的拷贝构造函数均指实际合成不是概念上合成,毕竟概念上只要没有且需要就合成。

  学习过后面后面三章后,再反过来看逐比特拷贝也叫memberwise copy,是将被拷贝对象的连续内存单元的内容拷贝到目标对象,所以当存在虚函数表或者虚基类表(这两现在有可能是一个表),这样的连续内存拷贝就会产生错误。(成员内容没有对齐)

  

  2.拷贝构造函数的工作

  ①对于前两中情况,合成的拷贝构造函数将调用成员类或者基类的拷贝构造函数

  ②对于第三种情况,当同类型赋值时逐位拷贝当然是可以的,但是当两个静态类型为基类,其中一个动态类型为派生类时,赋值需要改变类中虚函数表指针的指向。

  ③对于第四种情况,和第三种情况相同同类型赋值时逐位拷贝也是可以的,但是两个静态类型为基类,其中一个动态类型为派生类时,需要设定虚基类的初值或者简单确认其是否存在(确定其在运行期前有效)

 

二、基于拷贝构造函数的程序转换

  1.显示初始化操作:①重写定义②插入拷贝构造函数

X x1(x0);
X x2=x0;
X x3=X(x0);
//可能的转化如下
X x1,x2,x3 //重写定义
//编译器安插拷贝构造函数
x1.X::X(x0);
x2.X::X(x0);
X3.X::X(x0);

  2.参数的初始化:两种情况①导入临时性对象②拷贝构建

  第一种情况:①将函数改写为引用②在函数外部创建临时对象(方法与显示初始化相同,会调用拷贝构造函数),并将该对象传入改写后的函数

  第二种情况:已拷贝构建的方式直接把实际参数建构在需要的位置上(记录在堆上)

void foo( X x0);
X xx;
foo(xx);
//改写为
X temp;
temp.X::X(xx);
//函数改写为 void foo(X&);
foo(temp);

  3.返回值的初始化:与参数初始化类似分为两阶段①增加一个返回值类型的引用参数,改写返回类型为void,在外部建立一个临时值②在函数返回前调用拷贝构造函数传递值给增加的参数

//X foo()改写为
void foo(X &x)
{
    X xx;   //原始函数内要返回的局部变量
    //...
    x.X::X(xx); 拷贝构造
    return;
}

  第五章第一节提到,如果函数需要以值传递传回一个局部类对象,则建议提供一个拷贝构造函数(理由如上,需要用到拷贝构造函数),但是启动NRV优化后,将不需要调用拷贝构造函数了(所以如果你足够了解你的编译器,你可以不写拷贝构造函数)

//原始定义
Point foobar()
{
    Point local;
    return local;
}
//返回值优化
void foobar(Point &_result)
{
    Point local;
    local.Point::Point(0.0,0.0);
    _result=local;   //拷贝构造函数
    return;
}
//NRV优化
void foobar(Point &_result)
{
    _result.Point::Point(0.0,0.0);
    return;
}

  4.使用者层面优化:程序员自己定义一个特殊的构造函数用来仿照拷贝构造函数

 

  5.编译器层面优化:具名返回值Named Return Value(NRV)优化

  NRV优化:当把函数返回值直接赋值给一个对象时,可以想返回值初始化一样改变函数,但是不产生临时变量而是直接把被赋值的对象作为参数传递给函数。

  注:书上写只有拷贝构造函数存在时才会发生这种优化,但是现在的C++已经不要求这样了。

  参考资料:https://book.douban.com/annotation/19292671/

//原始要求
X x1=foo();
//下面这样也行
x1=foo();
//NRV优化
//X foo()改写为void foo(X&)
foo(x1);

  6.剔除拷贝构造函数:有时候编译器会剔除一些不必要的拷贝构造函数的操作

  参考之前写的一篇博客:https://i.cnblogs.com/posts/edit;postId=14657306

Nodefault x= Nodefault(1)

 

  上式仅调用构造函数,可以认为拷贝构造函数被剔除了。

 

posted @ 2021-06-16 22:18  放不下的小女孩  阅读(93)  评论(0编辑  收藏  举报