复制构造函数的构造操作

论坛 期权论坛 脚本     
已经匿名di用户   2022-7-2 21:58   1630   0
在三种情况下,会以一个对象的内容作为另一个对象的初值:
  • 定义一个对象并显示初始化
  • 对象作为参数传递给函数
  • 对象作为函数的返回值

和默认构造函数一样,当用户未显式定义复制构造函数时,编译器只有在某些条件下才会合成一个nontrivial的复制构造函数。下面主要讨论在哪些情况下,编译器才会自动合成一个复制构造函数。

当一个class没有声明一个复制构造函数,那么:
  • 当这个class不展现bitwise copy语义时,编译器会合成一个复制构造函数
  • 当这个class展现出bitwise copy语义时,编译器不会合成复制构造函数
那么什么是bitwise copy语义呢?看下面一个例子。
如果有一个如下所示的类:
class Foo {
public:
    int x, y;
};


那么编译器不会自动生成一个复制构造函数,因为对于这些内置类型,已经能够实现逐位拷贝了。但如果是下面这样的类:
class Foo {
public:
    int x, y;
    string str;
};


在这种情况下,编译器必须合成一个复制构造函数,函数内部调用str对象的复制构造函数,其它内置类型依旧逐位拷贝。

从上面两个例子可以得出结论:class中只存在整型、指针、数组等内置类型,则展现出bitwise copy语义。如果class中存在类类型,则不展现出bitwise copy语义。

一个class在以下4种情况不展现出bitwise copy语义:
1、类中有一个对象成员,并且该对象定义(显式定义或编译器合成)了复制构造函数。
2、类继承自一个基类,而基类存在一个(显式定义或编译器合成)复制构造函数。

上面两种情况比较好理解。对象成员或基类中有复制构造函数,所以编译器需要插入一些代码调用这些复制构造函数,这些代码被安插在合成的复制构造函数中。 注意,若已经显示定义了复制构造函数,则编译器不会执行插入操作

测试代码如下:
#include <iostream>
 
using namespace std;
 
class Foo {
public:
    Foo() {}     // 此构造函数必须定义
    Foo(const Foo &f) { cout << "Foo's copy construct!" << endl; }
};
 
class Bar: public Foo {
public:
    // 未定义复制构造函数
    int x, y;
    Foo foo;
};
 
int main()
{
    Bar ba;
    Bar bb = ba;
    return 0;
}


运行结果:

上述代码同时满足情况1、2,所以编译器会在合成的复制构造函数中调用了两次Foo类的复制构造函数。

3、当类中声明了虚函数
一说到虚函数,就会联想到virtual function table(vtbl)和vptr。在对象间进行复制时,对vptr的复制操作非常重要。虽说vptr是一个指针,可以按照逐位拷贝原则进行复制,但有时会发生很严重的错误:vptr都指向了同一个virtual function table。所以当编译器导入一个vptr后,为了正确初始化vptr,编译器需要合成复制构造函数进行相关操作。

具体来说,当有如下继承体系时:


当同类对象间进行复制初始化时,采用的是逐位拷贝,vptr指向相同的虚函数表。如下图所示:

这样的拷贝是安全的。

当用派生类初始化基类时,不能采用逐位拷贝,不同类的vptr指向各自的虚函数表,如下图所示:

不同类对象中的vptr指向了各自的vtbl,这是由合成出来的复制构造函数所要完成的任务。正因为如此,发生切割后无法实现多态性质。

测试代码:
#include <iostream>
 
using namespace std;
 
class Foo {
public:
    virtual void func()
    { cout << "virtual function in Foo!" << endl; }
};
 
class Bar: public Foo {
public:
    void func()
    { cout << "virtual function in Bar!" << endl; }
};
int main()
{
    Bar b1;
    Bar b2 = b1;    // vptr直接复制,指向相同的virtual function table
    b2.func();
 
    Foo foo = b1;   // 发生切割,vptr不直接复制,指向不同的virtual function table
    foo.func();
    return 0;
}


运行结果:

结果正如上面所说。

4、有虚拟基类的情况
一个对象以另一个同类的对象作为初值时,bitwise copy是有效的。
一个对象以其派生类的某个对象作为初值时,bitwise copy失效。

可以看到,编译器对复制构造函数的隐式操作和默认构造函数是相似的。编译器在需要某些特别的初始化操作时,才会合成复制构造函数。否则,对象之间直接进行逐位拷贝,编译器不会合成复制构造函数。

环境:
Win7 + VS2013

参考:
《深度探索C++对象模型》 P48-P60.
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:81
帖子:4969
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP