C++拷贝构造函数详解

有三种情况,会以一个object的内容作为另一个class object的初值:

  1. 对一个object做明确的初始化操作,像这样:

    class X{...};
    X x;
    //明确地以一个object的内容作为另一个class object的初值
    X xx = x;
    
  2. 当object 被当作参数交给某个函数时,例如:

    extern void foo(X x);
    
    void test(){
     X xx;
     foo(xx);    //以xx作为foo()第一个参数的初值(不明显的初始化操作)
    }
    
  3. 当函数传回一个class object时,例如:

    X foo_bar(){
     X xx;
     return xx;
    }
    

Default Memberwise Initialization

如果函数并没有提供一个explicit copy constructor,那么其拷贝同类型对象的操作由default memberwise initialization完成,其执行策略为:对每一个内建或派生的data member的值,从某一个object拷贝到另一个object。不过它不会拷贝其中的member class object,而是实施递归式的memberwise initialization(对每一个对象依次执行default memberwise initialization)。

Default constructors 和 copy constructors在必要的时候才由编译器产生出来。

Bitwise Copy Semantics(位逐次拷贝)

看看下面的程序段:

#include"Word.h"
Word noun("book");
void foo()
{
    Word verb = noun; //...
}

我们不可能预测这个初始化操作的程序行为。如果class Word的设计者定义了一个copy constructor,verb的初始化操作会调用它。但如果该class 没有定义explicit copy constructor,那么是否会有一个编译器合成的实体被调用呢?这就得视该class 是否展现"bitwise copy semantics”而定。

举个例子,已知下面的class Word 声明:

//以下声明展现了bitwise copy semantics
class Word{
public:
    Word(const char*);
    ~Word(){ delete []str;}     //..
private:
    int cnt;
    char *str;
};

这种情况下并不需要合成出一个default copy constructor,因为上述声明展现了“default copy semantics”。

再如下面的例子:

//以下声明并未展现出bitwise copy semantics
class Word{
public:
    Word(const String&);
    ~Word(){ delete []str;}     //..
private:
    int cnt;
    String str;
};

其中String声明了一个显示的拷贝构造函数

class String{
    public:
        String(const char*);
        ~String();
        //...
};

在这个情况下,编译器必须合成出一个copy constructor 以便调用member class String object的 copy constructor:

什么时候一个class不展现出“bitwise copy semantics”呢?有四种情况:

  1. 当class内含一个member object而后者的class声明有一个copy constructor时(不论是被class 设计者明确地声明,就像前面的String那样;或是被编译器合成,像class Word那样)。
  2. 当class继承自一个base class而后者存在有一个copy constructor时(再次强调,不论是被明确声明或是被合成而得)。
  3. 当class声明了一个或多个virtual functions时。
  4. 当class派生自一个继承串链,其中有一个或多个virtual base classes时。

前两种情况中,编译器必须将member或base class的“copy constructors调用操作”安插到被合成的copy constructor中。

虚函数指针

编译时期有两个程序的扩张操作:

■ 增加一个virtual function table(vtbl),内含每一个有作用的 virtual function的地址。
■ 将一个指向virtual function table的指针(vptr),安插在每一个class object内。

很显然,如果编译器对于每一个新产生的class objectvptr不能成功而正确地设好其初值,将导致可怕的后果。因此,当编译器导入一个vptrclass之中时,该class就不再展现bitwise semantics了。现在,编译器需要合成出一个copy constructor,以求将vptr适当地初始化,下面是个例子。

以一个派生类对象作为其父类对象的初始值,编译器需要”判断“

判断如何去拷贝构造

程序转化语义

下面的程序:

#include <iostream>

X foo(){
    X xx;
    // ...
    return xx;
}

可能会做出以下假设:

  1. 每次foo0被调用,就传回xx的值。
  2. 如果class X定义了一个copy constructor,那么当foo0被调用时,保证该copy constructor 也会被调用。

第一个假设的真实性,必须视class X如何定义而定。第二个假设的真实性,虽然也有部分必须视class X如何定义而定,但最主要还是视你的C++编译器所提供的进取性优化程度(degree of aggressive optimization)而定。你甚至可以假设在一个高品质的C++编译器中,上述两点对于class X的nontrivial definitions都不正确。

显式初始化操作(Explicit Initialization)

下面的例子是三种显示的拷贝构造操作:

void test() {
  X x0;
  X x1(x0);     // 第一种显示拷贝构造
  X x2 = x0;    // 第二种显示拷贝构造
  X x3 = X(x0); // 第三种显示拷贝构造
}

上面的程序转化可能会有两个阶段:

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

可能会变成下面这样

void test_Cpp()
{
    // ... x0的构造函数
    // 三个声明
    X x1,x2,x3;
    // 三个定义
    x1.X::X(x0);
    x2.X::X(x0);
    x3.X::X(x0);
}

参数的初始化(Argument Initialization)

C++标准说,把一个class object 当做参数传给一个函数,相当于一下形式的初始化操作:

(定义一个类X,作为test函数的参数)

X xx;
test(xx);

其中test函数的声明如下:

void test(X x0);

上面的参数xx这个变量作为参数传到函数test中,会产生一个临时对象,并且会调用拷贝构造函数将这个临时对象初始化,经过这些步骤这个参数才真正传入函数中进行使用。

所以上面的代码可能会转换为:

X __temp;   // 临时对象产生
__temp.X::X(xx);    //编译器对拷贝构造函数的调用
test(__temp);       //重新改写函数的调用操作

然而上面的做法只完成了一般,因为函数xx的声明还是一个传值的参数,test的声明也也须被转化,形式参数必须从原先的一个class X object改变为一个class X reference,像这样:

void test(X& x0);

返回值的初始化(Return Value Initialization)

例如下面的例子:

X test()
{
    X xx;
    return xx;
}

编译器可能会做如下NRV优化:

X __result;
void test(X& __result)
{
    __result.X::X();
    return;
}

当类显示Bitwise的情况下,应该不去定义一个拷贝构造函数,使用编译器合成就非常高效和安全!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345