深度探索C++对象模型——第二章

第二章 构造函数语意学

Default constructor的构造操作

带有Default constructor的成员类对象

C++合成的默认构造函数是面向编译器的而不是面向程序员的,只会合成编译器需要的对象而非程序员需要的对象,比如下面这个例子:

class Foo {
public:
  Foo() {
    std::cout << "Foo default ctor\n";
  }
};

class Base {
public:
  Foo foo;
  char *str;
};

int main() {
  Base b;
  if (b.str == 0) std::cout << "str not init\n";
  return 0;
}

编译器会产生默认构造函数初始化Foo但不会初始化str

如果一个类中包含的多个成员类对象有构造函数,编译器会生成默认构造函数,按照成员类对象声明的顺序进行初始化。

带有虚函数的基类

带有虚函数的类会生成默认的构造函数,应为在函数的编译器期间需要通过构造函数生成虚指针,通过虚指针生成虚表,否则在运行期间无法执行动态绑定。

带有虚继承的类

给带有虚基类的类生成默认构造函数,从而可以访问虚继承的父类的成员。

Copy Constructor操作

当合成的拷贝构造函数进行拷贝的时候,一个类的虚指针会显式的指向自己的虚表,而不是指向右侧对象的虚表。

class Base{
public:
    virtual void func();
};
class Derived : public Base {
public:
    void func() override;
};
Base b;
Derived d;
d = b;	//最终d的虚表指向的是b的虚表

bitwise copy不会出现的情况:

  1. 一个类的成员类中含有拷贝构造函数(编译器自动合成的也算)
  2. 一个类继承的父类中有拷贝构造函数
  3. 一个类有虚函数时
  4. 一个类继承串链中有虚基类时

因此对于虚指针的拷贝不能执行简单bitwise copy

bitwise copy是一种浅拷贝,对于拷贝的对象执行memcpy或者类似的操作,直接拷贝对象的指针,但是对于指针指向的内容不进行拷贝。

如果是同种类之间的赋值可以直接使用bitwise copy但是如果是不同类之间的赋值并且这个类是一个虚继承的子类时就不能使用bitwise copy,编译器会自动合成拷贝构造函数。

class Animal{...};
class Racoon : public virtual Animal {...};
class RedPanda : public Racoon {...};
Racoon a;
RedPanda b;
b = a;	//需要memeberwise,bitwise copy已经不够用了
Racoon c = a;	//bitwise copY完全够用
Racoon *pta;
Racoon pb = &pta;	//应为不知道pta具体指向的类型,只能拷贝Racoon的部分。

NRV优化

当给类加入拷贝构造函数时候编译器会执行NRV(named return value) 优化。没有的话则不执行。

class X {
    X();
};
X bar() {
    X xx;
    return xx;
};
X x = bar();	//这段代码中如果没有拷贝构造函数会进行宝贝构造两个临时变量等价于下面这段代码

X bar() {
    X xx;
    return xx;
}
X tmp = xx;
X x = tmp;	//这样做会影响程序的性能

但是加上拷贝构造函数之后,上面的代码就可以优化为:

X bar(X &result) {
    X xx;
    result(xx);
};

经过优化之后只需要进行一次构造函数。

如果一个类的成员都是trival类型,编译器会自动生成默认的拷贝构造函数,当我们自己声明拷贝构造函数的时候直接使用memset进行初始化效率更高也更快。

class Point3d {
public:
  Point3d(float x, float y, float z);
  Point3d(const Point3d &rhs) {
    _x = rhs._x;
    _y = rhs._y;
    _z = rhs._z;
  }
	//下面这种方式初始化效率更高
  Point3d(const Point3d &rhs) {
      memsett(this, &rhs, sizeof(Point3d));
  }
private:
  float _x, _y, _z;
}

但是如果一个类含有类成员或者是虚函数就不能在使用memset进行初始化,这样做会把类的虚指针也初始化掉。

class Base {
public:
  Base() {
    std::memset(this, 0, sizeof(Base));
  }
    //这段代码等效于
  Base() {
      _vptr_Base = _vtbl_Base;
      memset(this, 0, sizeof(Base));	//将vptr初始化为0
  }
  virtual void func();
  virtual ~Base();
private:
  int a;
};

posted on 2022-11-29 23:09  翔鸽  阅读(14)  评论(0编辑  收藏  举报