C++对象模型学习笔记2 构造函数语意学


2. 构造函数语意学

  • Schwarz Error

Jerry Schwarz,iostream函数库建构师,曾为了让cin能够求得一个真假值,定义了conversion运算符operator int()。这样用户写出:
if (cin)语句,就会很方便。
但当用户想要写cout << intVal时,不小心写成了cin << intVal; 结果编译器没有报错,哈哈,它的解析语义是:把cin转为整型,然后左移intVal
Jerry最后用operator void *()取代operator int()

这个故事告诉我们,编译器背后可能会隐式地做点什么。


问题1: 编译器是否为每个类都产生默认的构造函数?

class A {
  public:
    int a;
};

int foo() {
  A oa;
  return oa.a;
}

有些人以为这里对象oa的成员a值是零,其实它是个未定义值,初始化它是程序员的责任。这里编译器并不会为A产生任何构造函数

所以需要区别是编译器需要?还是程序的需要(用户需要)?

另外,即使编译器在它需要合成的情况下产生了一个默认构造函数,该函数也不会处理初始化的内容,仍然需要用户自行指定


问题2: 编译器在什么时候会合成默认的构造函数? {P41-P47}

  • 主要有四种情况,编译器需要合成一个隐式的默认构造函数 (implicit non-trivial default ctor),满足编译器的需要:
  1. 带有Default constructor的member class object
    1. 很容易理解,类成员对象在定义时,因为它有默认构造函数,那么它需要进行相应的构造,但何时调用呢?肯定是在本对象创建的时候,那么本对象得有一个构造函数去构造它们。
    2. 即使该函数已经具有一个构造函数了,编译器也需要在用户显式的语句前,安插代码来初始化
      eg: objA.A::A()
    3. 当有多个成员对象都需要调用时,调用顺序则为声明的顺序。
  2. 带有Default constructor的base class
    1. 容易理解,基类的内容创建时需要按照它指定的去初始化。
    2. 当用户已经有好多个ctor,但没有提供default ctor. 编译器会扩展当前的每一个ctor,将需要执行的工作放到每个ctor里,但不会再合成一个新的default ctor.
  3. 带有Virtual function的class
    1. 容易理解,初始化函数需要为产生的对象设置虚表指针
  4. 带有Virtual base class的class
    1. 跟3类似,需要为对象的布局初始化跟virtual base class相关的东西。

{P47} 至于没有存起那四种情况而又没有声明任何ctor的classes,我们说它们拥有的是implicit trivial default constructors,它们实际上并不会被合成出来。

一定要注意,合成出来的默认构造函数只会初始化编译器认为的必要的部分(基类对象,类对象),其他数据成员并不会被初始化

最后,要记住两个误区(都是不对的):

1. 任何类只要没有定义默认构造函数,就会被合成出来一个。
2. 编译器合成出来的默认构造函数,会为类内的每一个成员设定默认值。

{P48} 拷贝构造函数运用

以一个对象的内容作为另一个对象的初值 (进行初始化)

  1. 对一个对象做明确的初始化操作, eg: A objA1 = objA0
  2. 一个对象被当作参数交给某个函数时,eg:foo(objA);
  3. 函数返回一个对象时

跟构造函数一样,拷贝构造也分为trivial和non-trivial两种。只有non-trivial的实体才会被合成出来。决定一个拷贝构造是否为trivial的标准在于class是否展现出所谓的"bitwise copy semantics"。

{P53} Bitwise Copy Semantics(位逐次拷贝语意),说白了就是按照字节宽度的拷贝。有四种情况,一个class不展现出bitwise copy sema.

  1. 类成员对象声明有或被编译器合成有一个copy ctor时,这些对象需要显式定义拷贝构造(或编译器提供),说明字节复制无法满足需求
  2. 基类有copy ctor时 (显式或者编译器合成)
  3. 存在virtual函数
  4. 存在virtual继承的基类
前两种情况中,编译器必须将member或base class的copy ctor调用操作安插到被合成的copy ctor中。

没有拷贝构造函数的成员,将继续使用bitwise copy。

重点分析后两种情况,

{P54} 1. 重新设定virtual table指针

对于同一个类的对象进行拷贝构造,bitwise copy问题不大,对应vptr指向的是同一个类。
但可能存在情况是,B b = d (D继承B),这时候会发生切割,因为b实际的大小已经为B类的大小了,其 实际的虚表也需要指向B类本身的,
如果简单地进行bitwise copy,这时候的vptr就指向D,就错了!
细想下也不允许是d的vptr,假设有个虚函数foo,d的foo函数解析的是D大小的对象,而此时的b只有B类大小。而ref/point不会有这个问题,因为底层的大小还是没有变,该是D还是D。

{P57} 2. 处理Virtual Base Class suboject

类似的概念,同一个类对象不会有什么问题,但如果子类的对象去构造父类的对象,需要保证父类对象里vbase point/offset是对的。


显式初始化

{P61} 显式初始化操作会有两个必要的程序转化阶段:先重写每一个定义,剥除其中的初始化操作,然后安插class的copy ctor调用操作。
eg:

   X x1(x0)
      =>
   X x1;  	     定义被重写,初始化操作被删除 (仅留下内存占用 去掉初始化操作)
   x1.X::X(x0)   copy ctor

参数/返回值 重写

{P62-P63} 参数初始化/返回值初始化 编译器可以做一些函数签名引用重写,RVO等优化。

    foo(A);
    A a;
    foo(a)
=>
    A t;
    t.A::A(a);  调用copy ctor
    foo(t);     重写foo()函数调用,使用暂时对象。需要重写接口为引用效率更高
    A foo(){A x0; return x0;}
=>
    void foo(A &ret) {
      x.A::A(); // default ctor
      ret.A::A(x); // copy ctor
      return;
    }
=>
    void foo(A &ret) { 优化后
      ret.A::A();  
      return;
    }

    节省了一次copy ctor.

{P73} 有时候如果编译器的bitwise copy实现太差,可以考虑自己显式定义copy ctor,用memcpy等优化函数。 haha, 有这样的场合么???


初始化列表

  • {P74} 当写下一个ctor时,就有机会设定class members的初值。不是经由member initialization list,就是在constructor函数本身之内。
{P75} 必须使用初始化列表的场合:
  1. 初始化一个reference member时;
  2. 初始化一个const member时;
  3. 调用一个base class的constructor,而它拥有一组参数时;
  4. 调用一个member class的constructor,而它拥有一组参数时。
  • 可怕的事情:
    class Word {
      String _name;
      int _cnt;
      public:
        Word(){
          _name = 0;
          _cnt = 0;
        }
    }; 

这里的ctor会先产生一个临时的String obj并初始化String(0),然后以assignement运算符指定给_name, 然后临时变量再销毁。

  • 更佳的方式:
    Word():_name(0) {
      _cnt = 0;
    }

即: _name.String::String(0);

{P81} 编译器会对initialization list一一处理并可能重新排序,以反映出members的声明次序,它会安插一些代码到constructor内,并置于任何explicit user code之前。list项目顺序是由members声明顺序决定的,不是由init list里排列顺序决定。 {P77}

    class X {
        int i;
        int j;
    public:
        X(int val): j(val), i(j){}
    }

i值未定义,因为按照声明次序,i在j前, i = j 时 j 还未赋值为val。

    X::X(int val): j(val) {
      i = j;
    }

符合预期,因为init list先执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值