首页 > 代码库 > 模式识别 - 处理特征数据 及 代码

模式识别 - 处理特征数据 及 代码

和默认构造函数一样,当用户未显式定义复制构造函数时,编译器只有在某些条件下才会合成一个nontrivial的复制构造函数。所以,如果一个类未定义复制构造函数,编译器就自动为它产生出一个,这句话是错误的。下面主要讨论在哪些情况下,编译器才会自动合成一个复制构造函数。

如果有一个如下所示的类:
class Foo {
public:
    int x, y;
};


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


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

当没有显式定义复制构造函数,编译器在四种情况下会合成复制构造函数。

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指向各自的虚函数表,这就是为什么发生切割后无法实现多态性质的原因
测试代码:
#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、有虚拟基类的情况
虚基类在一个继承体系中只有一个实例存在,所以编译器必须保证程序在执行期确定虚基类的地址。所以,当对象进行复制初始化时,编译器必须执行某些操作来完成这种保证,以使不同对象的虚基类彼此分离。

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

环境:
Win7 + Code::Blocks

参考:
《深度探索C++对象模型》 P48-P60.