天天看点

d的复制构造函数

复制构造函数

概述

本文档提出了复制构造函数,它是后复制函数的替代方法.复制构造函数消除了对后复制的需求,并修复了其缺陷和固有限制.还讨论了演进和向后兼容性.

理由和动机

本节重点介绍了

后复制

存在问题,并说明了为什么

复制构造函数

后复制

好.

本(本)概述

无法有意义地

重载或限定

后复制函数.但,编译器不会拒绝限定器应用,如下所示:

构 A{本(本)常 {}}
构 B{本(本)不变{}}
构 C{本(本)共享{}}
           

存在限定词的情况下,未定义后复制的语义,实验表明该行为是打补丁式的:

  1. 常 不能修改目标中的任何字段的后复制
  2. 不变 永远不调用后复制(导致编译错误)
  3. 共享 后复制位不能保证原子性

    定义和实现

    有意义语义

    将破坏在当前语义下经过测试并认为正确的代码.

考虑常/不变后遗症

上面是一大堆问题的理由,不看了.

引入复制构造函数

如上所述,不严格限制,后复制很难检查类型,且同步成本过高.

该DIP提出具有以下优点的

复制构造

函数:

  1. 该特征在C++语言上效果很好[3];
  2. 可按普通构造函数检查

    复制构造函数

    类型(由于不位复制,字段初始化方式与

    普通构造函数

    相同);即按

    普通构造函数

    检查

    常/不变/共享

    类型.
  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

,

隐式

生成复制构造函数:

  1. S 未显式声明任何复制构造函数;
  2. 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
{
    本(ref 中 域 C b)//DIP前为普通构造函数,本dip后为复制构造函数
    {
        导入 std.stdio:writeln;
        writeln(" Yo ");
    }
}

空 函数(C c){}

空 main()
{
    C c;
    fun(c);
}