• 关于默认构造


    看下面这段代码:

     1 class Foo
     2 {
     3 public:
     4     int val;
     5     Foo *pnext;
     6 };
     7  
     8 void  foo_bar()
     9 {
    10     Foo bar;
    11     if(bar.val || bar.pnext)
    12         //do something....
    13 };

    在C++ARM中作者说道:“C++构造函数,在它需要时被编译器产生出来”;

    这里涉及到2个概念:

    1. 需要的时候;

    2. 编译器

    当然,我们编写程序时非常希望对象Foo拥有一个构造函数,自动将val和*pnext进行初始化。这和刚才的“需要的时候”是一个概念吗?

    no~前者是一个程序上的概念,而后者,则是编译器上的概念。

    什么时候才能自动创建构造函数呢?——编译器需要它的时候! 即使真的编译器需要它的时候,也不会初始化成员值为0。

    这里强调,如果用户没有定义构造,那么编译器100%会提供一个默认构造。但这个默认构造是“有用构造(nontrival)”还是“无用构造(trivial)”?看情况而定。

    无用构造内部无参,也不会自动初始化,当然这个操作由编译器完成,只是用来生成对象。

    以下我们讨论的,都是在某些情况下,编译器创建了“有用构造”。

    以下4种情况,编译器立功:

    1. 带有“默认构造函数”成员对象

    如果一个类没有构造函数,但内部有一个对象成员,该成员类有默认构造,那么这个类的默认构造将会由编译器合成。

    该合成操作只有在构造函数真正需要被调用时才发生。

    那么在C++不同的编译模块中,编译器如何避免合成出多个默认构造:

       1:  class Foo
       2:  {
       3:  public:
       4:       Foo();
       5:       Foo(int);
       6:  };
       7:   
       8:  class Bar
       9:  {
      10:  public:
      11:       Foo foo;
      12:       char *str;
      13:  }
      14:   
      15:  void foo_bar()
      16:  {
      17:       Bar *bar;   //Bar::foo must init here
      18:       if(str)
      19:          //do something....
      20:  }
     

    被合成的Bar类的默认构造内部有必要代码,用来调用Foo类默认构造来处理成员对象Bar::foo,但它并不产生代码来初始化Bar::str。

    是的,将Bar::foo初始化是编译器的责任。因为Foo都有自己的默认构造了,还需要我们来多此一举吗?但将Bar::str初始化则是程序的责任。

       1:  inline Bar::Bar()
       2:  {
       3:       //被合成的默认构造可能会这样    
       4:       foo.Foo::Foo();
       5:  }

    但要注意,这个默认构造只是满足编译器的需要。

     

    ※如果类A内部含有1个或1个以上的成员对象,那么A的每一个构造都必须调用每一个成员对象的默认构造

    所以,就算你的代码是这样的:

       1:  Bar::Bar()
       2:  {
       3:       str = 0;
       4:  }
    编译器也会这么做:
       1:  Bar::Bar()
       2:  {
       3:       foo.Foo::Foo(); //编译器自动加入的编译器代码
       4:       str = 0;
       5:  }
    当然,如果成员对象很多,那么就按照声明次序由编译器依次添加构造调用

     

    2. 带有“默认构造函数”基类

    类似的道理,如果一个无任何构造的派生类继承于有默认构造的基类,那么这个派生类的默认构造由编译器生成。

       1:  class Base
       2:  {
       3:       Base();
       4:       // other member
       5:  }
       6:   
       7:  class A : public Base
       8:  {
       9:       // other member
      10:  }

    它将调用基类的默认构造来合成自己的默认构造。

    伪代码或许如下:

       1:  A::A()
       2:  {
       3:       Base::Base();
       4:       // other something....
       5:  }

     

    3. 带有“一个虚函数”

    1. class声明或继承一个virtual function

    2. class派生自一个继承串链,其中有一个或更多的virtual base classes

    此时,该类也需要自动合成出默认构造,该操作由编译器完成。

       1:  class Widget
       2:  {
       3:       virtual void flip();
       4:       //....
       5:  }
       6:   
       7:  void flip(const Widget &widget) {widget.flip();};
       8:   
       9:  //假设Bell和Whistle都派生自Widget类
      10:  void foo()
      11:  {
      12:       Bell b;
      13:       Whistle w;
      14:   
      15:       flip(b);
      16:       flip(w);
      17:  }

    Widget/Bell/Whistle在编译期间会发生的操作:

    1. 一个virtual function table(vtbl)会在编译器产生出来,内放class的virtual function地址;

    2. 在每一个class object中,一个额外的pointer member(vptr)会被编译器合成出来,内含相关的class vtbl地址;

    这里,因为Widget &widget使用了引用操作,所以产生了多态。内部使用了widget的vptr和vtbl中的flip()条目,最终指向的对象为Bell或Whistle。

     

    4. 带有“一个虚基类”

    虚基类的实现方法在不同编译器有极大差异。

       1:  class X { public: int i; };
       2:  class A : public virtual X { public: int j; };
       3:  class B : public virtual X { public: double d; };
       4:  class C : public A, public B { public: int k; };
       5:   
       6:  //无法在编译时期确定出实际类型
       7:  void foo(const A* pa) {pa->i = 1024; };
       8:   
       9:  main()
      10:  {
      11:       foo(new A);
      12:       foo(new C);
      13:  };
  • 相关阅读:
    图书馆管理系统

    有理数类的设计
    题目4-多关键字排序(基于自定义比较函数)
    图总结
    树、二叉树、查找算法总结
    数据结构小结
    C语言文件
    第二次博客作业
    第一次博客作业
  • 原文地址:https://www.cnblogs.com/davidsguo008/p/3651886.html
Copyright © 2020-2023  润新知