概述
本文档提出了复制构造函数,它是后复制函数的替代方法.复制构造函数消除了对后复制的需求,并修复了其缺陷和固有限制.还讨论了演进和向后兼容性.
理由和动机
本节重点介绍了后复制
存在问题,并说明了为什么复制构造函数
比后复制
好.
本(本)概述
无法有意义地重载或限定
后复制函数.但,编译器不会拒绝限定器应用,如下所示:
构 A{本(本)常 {}}
构 B{本(本)不变{}}
构 C{本(本)共享{}}
存在限定词的情况下,未定义后复制的语义,实验表明该行为是打补丁式的:
- 常 不能修改目标中的任何字段的后复制
- 不变 永远不调用后复制(导致编译错误)
- 共享 后复制位不能保证原子性
定义和实现有意义语义
将破坏在当前语义下经过测试并认为正确的代码.
考虑常/不变后遗症
上面是一大堆问题的理由,不看了.
引入复制构造函数
如上所述,不严格限制,后复制很难检查类型,且同步成本过高.
该DIP提出具有以下优点的复制构造
函数:
- 该特征在C++语言上效果很好[3];
- 可按普通构造函数检查
复制构造函数
类型(由于不位复制,字段初始化方式与普通构造函数
相同);即按普通构造函数
检查常/不变/共享
类型. - 提供封装.
缺点是用户必须手动复制所有字段,且每次加字段到构时,必须修改复制构造
函数.但,使用D的自省机制
可轻松搞定.如,此简单代码可用作语言习语或库函数:
foreach(i,ref field;src.tupleof)
本.tupleof[i]=字段;
如上所示,可用几行代码轻松替换后复制的好处.如下详述,复制构造函数以最小语言复杂性解决后复制的问题.
描述本节讨论有关复制构造函数语义的所有技术方面.
句法
在构
定义内部,如果函数声明中第一个参数与构类型的非默认引用相同,其他所有参数都具默认值时
,则为复制构造函数.这种方式声明复制构造函数
,优点是不用改解析器,语法可不变.例:
导入 std.stdio ;
构 A
{
本(引用 中 域 A rhs){writeln(" x "); } //复制构造函数
本(引用 中 域 A rhs,int b = 7)不变 {writeln(b); } //使用默认参数的复制构造函数
}
空 main()
{
A a;
A b = a; //隐式调用复制构造函数,打印" x"
A c = A(b); //显式调用构造函数
不变 A d = a;//隐式调用复制构造函数-打印7
//为什么打印7呢?,因为有`不变`
}
可显式
调用复制构造函数(如上面的c),因为它也是先前语言语义中存在的构造函数.
按引用
传递复制构造函数的参数,避免无限递归(按值传递需要复制构,导致无限递归调用复制构造).注意,如果源是右值,则不需要调用复制构造函数,因为将按位移动(如必要)到目标中(即,优先移动构造).例:
构 A { 本(引用 中 域 A rhs){}}
A 创建()
{
静 A 原;中 原;
}
空 main()
{
A 值=create();
}
仅调用一个复制构造
,在rhs位置的原
.然后,把右值放入值
.
可对复制构造函数参数/函数本身
应用类型限定器
,以允许定义跨不同可变级
对象拷贝.类型限定器是可选的.
语义学
本节讨论复制构造函数和其他语言特征间的语义分析和交互
关系.
复制构造函数和后复制共存
为确保后复制
到复制构造函数的平稳过渡,此DIP提出以下策略:如果一个构定义了(用户定义或生成
的)后复制,则忽略复制构造函数,优先后复制.现有的不使用后复制
代码库可开始使用复制构造函数
,而当前依赖后复制的代码库可使用复制构造函数
开始编写新代码并删除后复制
.本DIP建议弃用后复制,但未规定弃用时间表.
从后复制到复制构造函数语义上相近的转换如下:
//新代码
构 A
{
本(引用 中 域 进出 A rhs)进出 { ... }
}
//替换现有代码
构 A
{
本(本){ ... }
...
}
复制构造函数用法
每当将构变量复制另一个相同类型变量时,编译器隐式插入复制构造函数调用:
显式初化变量时:
构 A
{
本(引用 中 域 A rhs){}
}
空 main()
{
A a;
A b = a; //称呼复制构造函数为赋值
b = a; //而不是初化
}
按值
传递参数给函数时:
构 A
{
此(引用 中 域 A a){}
}
空 函数(A a){}
空 main()
{
A a;函数(a);//调用复制构造函数
}
从函数按值返回参数且无NRVO时:
构 A
{
本(引用 中 域 A a)
{
writeln(" cp构造器 ");
}
}
A 函数()
{
A a;中 a;
}
A a;
A 枪()
{
中 a;
}
空 main()
{
A a=函数();// NRVO未调用复制构造函数
A b=枪();//无法NRVO,调用复制构造函数
}
按引用
传递复制构造函数参数,仅当源是左值时,降低初始化至复制构造函数调用.尽管可转发临时左值声明至复制构造函数,但本DIP不讨论绑定右值到左值.
注意,函数返回定义了复制构造函数的构实例且无法NRVO时,在返回前,在调用点调用复制构造函数.如可NRVO,则删除复制:
构 A
{
本(引用 中 域 A rhs){}
}
A a;
A 函数()
{
中 a;//降级返回tmp.复制构造器(a)
//中 A();//右值,不调用复制构造器
}
空 main()
{
A b = 函数();//用函数的返回值原位构造b
}
检查类型
复制构造函数与构造函数的类型检查[6][7]一样.
可显式禁用
复制构造函数重载
:
构 A
{
@禁用 本(引用 中 域 A rhs);
本(ref 中 域 不变 A rhs){}
}
空 main()
{
A b;
A a = b;//错误:禁用复制构造
不变 A ia;
A c = ia;//好
}
为了禁用复制构建,必须禁用所有复制构造函数重载.在上面示例中,仅禁用了从可变到可变的复制;仍可调用从不变到可变复制重载.
重载
可用(从合格的源复制的
)参数的不同限定器或复制构造函数自身(复制到合格目标)来重载
复制构造函数:
构 A
{
本(ref 中 域 A b){}//-可变源,可变目标
本(ref 中 域 不变 A b){}// 2-可变源,可变目标
本(ref 中 域 A b)不变 {}// 3-可变源,不变目的地
本(引用 中 域 不变 A b)不变{}//4不变源不变目标
}
空 main()
{
A a;不变 A ia;
A a2 = a; //调用1
A a3 = ia; //调用2
不变 A a4 = a; //调用3
不变 A a5 = ia; //调用4
}
使用户能任意组合限定:常,不变,共享,常 共享
.
进出
用于限定变,常或不变
相同的类型:
构 A
{
本(引用 中 域 进出 A rhs)不变 {}
}
空 main()
{
r1;
常(A)r2;
不变(A)r3;
//都调用相同复制构造函数,因为`进出`行为就像通配符//三种情况的通配符
不变(A)a = r1;
不变(A)b = r2;
不变(A)c = r3;
}
部分匹配时,适用现有重载和隐式转换规则
.
复制构造函数调用与位刷
如果构未定义复制构造函数,则右边存储位置位刷
到左边来初化.例:
构 A
{
int [] a;
}
空 main()
{
A a = A([ 7 ]);
A b = a; // mempcy(&b,&a)
不变 A c = A([ 12 ]);
不变 A d = c; // memcpy(&d,&c)
//复制内存(汇,源);
}
构定义复制构造函数时,将对该构禁用所有隐式位刷.例:
构 A
{
int [] a;
本(引用 中 域 A rhs){}
}
空 函数(不变 A){}
空 main()
{
不变 A a;
函数(a);//错误:不能用`(不变)不变`类型调用复制构造函数
//没有定义`不变(不变)`复制构造函数吧.
}
与别名 本
交互
构定义别名 本
及复制构造函数
可能冲突.如返回别名 本
类型是定义类型的特定
版本时,有歧义.例:
构 A
{
int * a;
不变(A)函数()
{
中 不变 A();
}
别名 函数 本;
本(引用 中 域 A)不变 {}
}
构 B
{
int * a;
不变(B)函数()
{
中 不变 B();
}
别名 函数 本;
本(ref 中 域 B b){}
}//将这三个属性合并为一个.
空 main()
{
A a;不变 A ia = a;//复制构造函数
B b;不变 B ib = b;
//错误:`()不变`参数类型可能
//不会调用复制构造函数
}
虽然复制构造函数和别名 本
都适合解决赋值问题时,复制构造函数比别名 本
优先级更高,因为它更特定(复制构造函数目的就是创建副本
).如果未匹配重载集中的复制构造函数,则发出错误,即使使用从别名 本
理论上讲也可生成匹配构造器.即,在复制构造
上,忽略别名 本
.
生成复制构造函数
如满足以下所有
条件,编译器对构 S
,隐式
生成复制构造函数:
- S 未显式声明任何复制构造函数;
- S 至少有一个定义具有
复制构造函数
的直接成员,且该成员不与任何其他成员通过联
重叠.
如满足上述限制,则生成以下复制构造函数:
本(引用 中 域 进出(S)src)进出
{
foreach(i,ref 进出 field;src.tupleof)
本.tupleof[i]=字段;
}
普通旧数据
定义复制构造函数的构不是POD.
与联的交互
如果联 S
具有定义复制构造函数的字段,则每当S按复制初化类型对象
时,发出错误.重叠字段
(匿名联合)也这样.
重大变更和弃用
按源对象可变引用传递复制构造函数的参数
.即复制构造函数调用可合法修改源对象:
构 A
{
int [] a;
本(引用 中 域 A b)
{
b.a[2]=3;
}
}
空 main()
{
A a,b;
a = b; //修改ba[2]
}
允许非常
的参数,不然,太麻烦,还要常
为参数,里面再把常
去掉.折腾.c++不推荐.
构 C
{
本(ref 中 域 C b)//DIP前为普通构造函数,本dip后为复制构造函数
{
导入 std.stdio:writeln;
writeln(" Yo ");
}
}
空 函数(C c){}
空 main()
{
C c;
fun(c);
}
具有上述定义构造函数只用作复制构造函数
.