zoukankan      html  css  js  c++  java
  • Default Constructor的构建操作

     在《C++ Annotated Reference Manual(ARM)[ELLIS90]》中的Section 12.1告诉我们:"Default constructors...在需要的时候被编译器产生出来"。
    其实默认构造函数也是分为两类的:有用的(nontrivial )、无用的(trivial )。
    所谓有用的标准也是就默认构造函数会为我们的类做一些初始化操作。那么无用的就不会做任何工作,从而对我们的类也就没有任何意义。
    所以,我们通常所说的默认构造函数是指有用的默认构造函数,其英文名字叫nontrivial default constructor。
    例如下面的代码:
    class Foo {
    public: 
        int val;
        Foo *pnext;
    };
    
    void f00_bar()
    {
        Foo bar;  //程序要求bar's members都被清为零,不是编译器需要
        if(bar.val || bar.pnext)
            //...do something
    }
    

    编译器并不会为上述代码生成一个默认的构造函数,只有在下面四种情况下,编译器才会生成默认构造函数(nontrivial default constructor):

    (1) 如果一个类里面某个成员对象有nontrivial default constructor,编译器就会为我们的类产生nontrivial default constructor

    class Foo {
     public:
        Foo();
        Foo(int);
        ...
    };
    
    class Bar {
    public:
        Foo foo;
        char *str;
    };
    
    void foo_bar()
    {
        Bar bar;
        if(str) {}
        ...
    }
    

     被合成的Bar default constructor 内含必要的代码,能够调用class Foo的default constructor 来处理member object Bar::foo,但它并不产生任何代码来初始化Bar::str。被合成的default constructor看起来可能像这样:

    inline Bar::Bar()
    {
        //C++伪代码
        foo.Foo::Foo();
    }
    

    如果程序员定义了default constrcutor

    Bar::Bar() 
    {
        str = 0;
    }
    

    现在程序员的需求满足了,但是编译器还需要初始化member object foo。编译器会扩张现存的constructor,在其中安插一些代码,使得user code在被执行前,先调用必要的default constructors。

    Bar::Bar()
    {
        foo.Foo::Foo(); //附加上的compiler code
        str = 0;
    }
    

    如果有多个class member objects都要求constructor初始化操作,C++语言要求以 “member objects在clsss中的声明次序”来调用各个constructor。

    class Dopey   {public: Dopey(); ...};
    class Sneezy  {public: Sneezy(int); Sneezy(); ...};
    class Bashful  {public: Bashful(); ...};
    
    class Snow_White{
    public:
        Dopey dopey;
        Sneezy Sneezy;
        Bashful bashful;
        ....
    private:
        int mumbae;
    };
    

    如果Snow_White没有定义default constructor,就会有一个nontrivial constructor被合成出来,一次调用Dopey、Sneezy、Bashful的default constructor。如果定义了下面这样的default constructor:

    Snow_White::Snow_White() : sneezy(1024)
    {
        mumble = 2048;
    }
    
    他会被扩展为:
    
    Snow_White::Snow_White()
    {
      //按照声明的顺序调用其constructor
        dopey.Dopey::Dopey();
        sneezy.Sneezy::Sneezy();
        bashful.Bashful::Bashful();
    
        mumble = 2048;
    }
    

    (2) 如果一个派生类的基类有nontrivial default constructor,那么编译器会为派生类合成一个nontrivial default constructor

    编译器这样的理由是:因为派生类被合成时需要显式调用基类的默认构造函数。

    如果设计者提供多个constructor,但其中都没有调用default constructor,编译器不会合成一个新的default constructor,它会扩张现有的每一个constructor,将“用以调用所有必要之default constructor”的程序代码加进去。

    (3) 如果一个类带有一个Virtual Function,那么编译器会为派生类合成一个nontrivial default constructor

    编译器这样做的理由很简单:因为这些vtbl或vptr需要编译器隐式(implicit)的合成出来,那么编译器就把合成动作放到了默认构造函数里面。所以编译器必须自己产生一个默认构造函数来完成这些操作。
    所以如果你的类里带有任何virtual function,那么编译器会为你合成一个默认构造函数。
    (4) 如果一个类虚继承于其它类,那么编译器会为派生类合成一个nontrivial default constructor

    编译器这样做的理由和(3)类似:因为虚继承需要维护一个类似指针一样,可以动态的决定内存地址的东西(不同编译器对虚继承的实现不全相同)。

    class X {public: int i; };
    class A : public virtual X {public: int j; };
    class B : public virtual X {public: double d; };
    class C : public A, public B {public: int k; };
    
    
    //无法在编译时期决定出pa->X::i 的位置
    void foo(const A* pa) { pa->i = 1024; }
    
    mian()
    {
        foo(new A);
        foo(new C);
        ...
    }
    

    编译器无法固定住foo()之中“经由pa而存取的X::i”的实际偏移量,以为pa的真正类型可以改变。foo()可以被改写如下:

    void foo(const A* pa) { pa->_vbcX->i = 1024; }
    

    其中,_vbcX表示编译器所产生的指针,指向virtual base class X。  

    总结

    上面四种分析合成出的default constructor都是nontrivial default constructors,不在此情况之内的都trivial default constructors,它们实际上并不会被编译器合成出来
    在合成的default constructors,只有base class subobjects和member class object会被初始化,所有其它的nonstatic data member,如整数、整数指针、整数数组等等都不会被编译器初始化。
    C++新手常见的两个的错误
    (1)  任何class 如何没有定义default constructor,就会被合成出来一个
    (2)  编译器合成出来的default constructor会明确设定class 内每个data member 的默认值
  • 相关阅读:
    说一说前端路由与后端路由的区别
    HTTP 8种请求方式介绍
    JavaScript 运行机制以及Event Loop(事件循环)
    常见的数据结构
    JS中常见的几种继承方法
    JS异步编程
    js中的数据类型,以及如何检测数据类型
    js面试题之手写节流函数和防抖函数
    前端面试题
    (八) SpringBoot起飞之路-整合Shiro详细教程(MyBatis、Thymeleaf)
  • 原文地址:https://www.cnblogs.com/jianxingzhe/p/4009142.html
Copyright © 2011-2022 走看看