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++不推荐.

构 C
{(ref 中 域 C b)//DIP前为普通构造函数,本dip后为复制构造函数
    {
        导入 std.stdio:writeln;
        writeln(" Yo ");
    }
}

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

具有上述定义构造函数只用作复制构造函数.

发布了377 篇原创文章 · 获赞 25 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/fqbqrr/article/details/104327110