首页 > 其他 > 详细

构造函数语意学

时间:2019-12-25 23:24:04      阅读:97      评论:0      收藏:0      [点我收藏+]

  对于class X,如果没有任何user-declared constructor, 那么会有一个default constructor被隐式(implicit)声明出来,但是这个default construct是无用的。关键词explicit被导入,就是给我们提供一种方法,他们能够制止“单一参数的construct”被当成一个conversion运算符。

一、Default Construct的构造操作

class Foo
{
   public:
         int val;
         Foo *pnext;       
};      

  default construct在编译器需要的时候自动产生一个,而在程序需要的时候编译器不会产生,因为这是程序员的责任。

void foo_bar()
{
    Foo bar;
    if(bar.val||bar.pnext)
        //do something
    //...
}

  上述代码并不会自动生成default construct,所以要想使程序正常工作,必须显示的声明一个default construct。

  Global object的内存保证会在程序启动时候被请清为0,。local object配置于程序的堆栈中,heap object配置于自由空间中,都不一定会被清为0,它的内容是上次使用后的。

1.“带有Default Construct”的Member Class Object

  如果一个class没有任何construct,但是它含有一个member object,而后者有default construct,那么这个class的implicit default construct是有用的,编译器会合成一个default construct,只不过这个construct真正被调用的时候才会发生。但是c++有不同的编译模块,编译器为了避免合成多个default construct,把合成的default construct,copy construct,destructor,assignment copy operator都以inline的方式完成。

class Foo {public:Foo(), Foo(int) };
class Bar {public: Foo foo;char *str;}//内含

void fun()
{
    Bar bar;//此时会合成默认构造函数,会调用Bar的默认构造函数
}

//编译器在Bar中插入代码,合成default construct
inline Bar::Bar()
{
    foo.Foo::Foo();
}; //但不会初始化str,需要程序员来进行初始化;

  编译器合成的default construct只是满足编译器的需要,而不会满足程序的需要,所以bar.str并不会被初始化,要想初始化此成员,要手工定义default construct。

  但是如果程序员自己定义构造函数,如下,构造函数被现实定义,那么编译器就不会再合成第二个default construct。

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

  那么为了编译器的需要,编译器会扩充已存在的construct(如下)。也就是:如果class A内含有一个或一个以上的member class object,那么class A的每个construct都必须调用每个member classes的default construct。在user conde被调用之前,先调用default construct。

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

  如果有多个class member objects都要求contructor初始化操作。C++语言要求以“member objects在class中的声明顺序“来调用各个constructors。这一点由编译器完成,它为每个constructor安插程序代码,以”member声明顺序“调用每一个member所关联的default constructors。这些代码将被安插在explicit user code之前。

class Dopey {public: Dopey();...};
class Sneezy {public: Sneezy(int); Sneezy();...};
class Bashful {public: Bashful();...};

//以及一个class Snow_White:
class Snow_White {
public:
    Dopey dopey; 
    Sneezy sneezy;
    Bashful bashful;

private:
    int mumble;
};

//如果Snow_White没有定义default constructor,就会有一个nontrivial constructor被合成出来,
//依序调用Dopey、Sneezy、Bashful的default constructors。然而如果Snow_White定义了下面这样
//的default constructor:
//程序员所写的default constructor
Snow_White::Snow_White(): sneezy(1024)
{
    mumble = 2048;
}

//编译器扩张后的default constructor
Snow_White::Snow_White():sneezy(1024)
{
    //插入member class object
    //调用其constructor
    dopey.Dopey::Dopey();
    sneezy.Sneezy::Sneezy(1024);
    bashful.Bashful::Bashful();

    //expilict user code
    mumble = 2048;
}

  总结:

  1. 如果类无member object,那么会在编译器需要的时候产生一个无用的default construct。
  2. 如果类有member object,1.如果类无default construct,那么编译器会自动合成一个,初始化member object,但是类的member data的初始化任务合成的构造函数不会做;2.如果类有construct,那么编译器会在现有的construct加入一些代码,调用member object的default construct。

2."带有Default Constructor“的Base Class

  类似的道理,如果没有任何constructors的class派生自一个”带有default constructor“的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。它将调用上一层base classes的default constructor(如果是多继承,根据他们声明的顺序)。对一个后继的class而言,这个合成的constctor和一个”被显式提供的default constructor“没有什么差异。

  如果设计者提供多个constructors,但其中都没有default constructor呢?编译器会扩张现有的每一个constructors,将“用以调用所有必要之default constructors”的程序代码加进去。它不会合成一个新的default constructor,因为其他“由user所提供的constructors”存在的缘故。如果同时亦存在着“带有default constructor”的member class objects,那些default constructor也会被调用--在所有base class constructor之后。

3.“带有一个virtual Function”Class

  另有两种情况,也需要合成出default constructor:

  1. class声明(或继承)一个virtual function  
  2.  class继承自一个继承串链,其中有一个或多个的virtual base classes

  假设基类中还有虚函数,派生类继承基类时编译期间会发生两步扩张活动:

  1.  一个virtual function table(在cfont中被称为vtbl)会被编译出来,内放class的virtual functions地址
  2. 在每个class object中,一个额外的pointer member(也就是vptr)会被编译器合成出来,内含相关之class vtbl的地址

  编译器会为每一个派生类的vptr设定初始值,放置适当的virtual table地址,对于class定义的每个construct,编译器都会安插一些代码做这些事,对于未声明construct的class,编译器会合成construct来做这些事。

4.“带有一个virtual Base Class”的Class

  virtual base class的实现在不同编译器间有很大的差异。

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;
};

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

int main()
{
    foo(new A);
    foo(new C);
}

  编译器无法固定住foo中“由pa而存取的X::i”的实际偏移位置,因为pa的真正类型可以改变,编译器必须改变“执行存取操作”的那些代码。使得X::i延迟到执行期才决定下来。cfront的做法是在derived class object的每一个virtual base classes中安插一个指针完成。所有经由reference或pointer来存取的一个virtual base class的操作通过相关的指针来完成。

//可能编译器转变的操作
void foo(const A* pa)
{
    pa->_vbcX->i=1024;//_vbcX表示编译器所产生的指针,指向virtual base class X,在class object的构造期被完成
}

  对于class 所定义的每个construct,编译器会安插一些“允许每个virtual base class的执行期存取的操作”的代码,如果class没有声明任何constructors,编译器必须为它合成一个default constror。

总结,有4种情况,会造成“编译器必须为未声明constructor 的classes合成default constructor”。它们分别是:

  1. “带有Default Constructor”的Member Class Object
  2. “带有Default Constructor”的Base Class
  3. “带有一个Virtual Function”的Class     
  4. “带有一个Virtual Base Class”的Class

  C++Standard把那些合成物称为implicit nontrivial default constructors。被合成出来的constructos只能满足编译器(而非程序)的要求。它之所以完成任务,是借着“调用member object或base object的default constructor”或是“为每一个object初始化其virtual function机制或virtual base class机制”而完成的。至于没有存在那4种情况而又没有声明任何constructor的classes,我们说它们拥有的是implicit trivial default constructors,它们实际上并不会被合成出来。

  在合成default constructor中,只有base class constructor和member class objects会被初始化。所有其他的nonstatic data member(如整数,整数指针、整数数组等等)都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则非必要。如果一程序需要一个“把某指针设为0”的default constructor,那么提供它的人应该是程序员。

  C++新手一般有两个常见的误解:

  1. 任何class如果没有定义default constructor,就会被合成出一个来。
  2. 编译器合成出来的default constructor会显式设定“class内每一个data member的默认值”  

  如你所见没一个是真的。

构造函数语意学

原文:https://www.cnblogs.com/tianzeng/p/12099034.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!