【C++对象模型】构造函数II

构造函数语意学

》》构造函数语意学I—默认构造函数的构造操作《《
》》构造函数语意学II—拷贝构造函数的构造操作《《
》》构造函数语意学III—程序转化语意学《《

拷贝构造函数的构造操作

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

1.直接拷贝

class X{...};
X x;
X xx = x;

2.函数传参

void foo(X x){}
X xx;
foo(xx);

3.函数返回

X foobar()
{
    X xx;
    ...
    return xx;
}

假设class设计者显式定义了一个拷贝构造函数那么在大部分情况下,上述的拷贝构造函数会被调用。

默认成员初始化

如果class没有提供一个显式拷贝构造函数时,其内部是以所谓的默认成员初始化手法完成的,也就是把每一个内建的或派生的成员数据(例如一个指针或一个数组)的值,从某个对象拷贝一份到另一个对象身上。不过它并不会拷贝其中的成员类对象,而是以递归的方式施行成员初始化。

ARM所说:

默认构造和拷贝构造在必要的时候才由编译器产生出来。

这个句子中的“必要”意指当 class 不展现位逐次拷贝时。一个对象可用两种方式复制得到,一种是被初始化,另一种是被指定。从概念上而言,这两个操作分别是以拷贝构造和拷贝赋值运算符(=)完成的。

位逐次拷贝

C++标准把拷贝构造区分为平凡和非平凡两种。只有非平凡的实例才会被合成于程序之中。决定一个拷贝构造是否为平凡的标准在于class是否展现出所谓的“位逐次拷贝”。

以下情况并不需要合成出一个默认拷贝构造,因为上述声明展现了“位逐次拷贝”

class World{
public:
    World(const char *);
    ~World(){delete [] str;}
private:
    int cnt;
    char *str;
}

这种情况下,编译器必须合成出一个拷贝构造,以便调用String的拷贝构造

class World{
public:
    World(const string&);
    ~World(){delete [] str;}
private:
    int cnt;
    string str; //string含有显式拷贝构造
}

什么时候一个class展现出“位逐次拷贝”呢?有4种情况:

  1. 当class内含一个成员对象而后者的class声明有一个拷贝构造时
  2. 当 class继承自一个基类而后者存在一个拷贝构造时
  3. 当 class声明了一个或多个虚函数时。
  4. 当 class派生自一个继承串链,其中有一个或多个虚基类时。

重新设定 Virtual Table的指针

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

  1. 增加一个虚函数表(vtbl),内含每一个有作用的虚函数的地址。
  2. 一个指向虚函数表的指针(vptr),安插在每一个 class object内。

如果编译器对于每一个新产生的class object 的vptr不能成功而正确地设好其初值,将导致可怕的后果。

class Bear : public ZooAnimal{...};
//ZooAnimal内含有虚函数animal(),draw()
//Bear内含有虚函数animal(),draw(),dance()
Bear yogi;
Bear winner = yogi;

yogi会被默认Bear构造初始化。而在构造函数中,yogi的vptr被设定指向Bear class 的 virtual table (靠编译器安插的代码完成)。因此,把 yogi的vptr值拷贝给winnie的vptr是安全的。

【C++对象模型】构造函数II_第1张图片

ZooAnimal franny = yogi;//发生切割行为

franny的vptr不可以被设定指向Bearclass的virtual table(但如果yogi的vptr被直接“逐次拷贝”的话,就会导致此结果),否则当上面程序片段中的draw()被调用而franny被传进去时,就会“炸毁”
【C++对象模型】构造函数II_第2张图片

让我再以上面这张图来补充说明yogi和franny的关系
也就是说,合成出来的ZooAnimal拷贝构造会显式设定object的vptr指向ZooAnimalclass的virtual table,而不是直接从右手边的class object中将其vptr现值拷贝过来。


你可能感兴趣的:(c++,c++,开发语言)