构造函数产生的点及原因

我相信很多人对构造函数在什么时候产生,以及产生的原因,理解得不是很透彻;更有甚者认为默认构造函数和复制构造函数是一定会产生的,成员变量就应该在初始化参数列表中进行初始化,当然这些是初学者的认识,下面分享一下我的看法。

构造函数不负责分配内存,只是在分配好的一块内存中进行赋值操作.这一点我们可以很容易从new/delete与malloc/free的区别中看出来,malloc/free只负责分配内存不负责初始化,而new/delete不仅负责分配内存,如果对象存在相应的够着函数,就会调用相应的构造函数,如果不存在当然就不调用,如int *i=new int[10];int类型没有构造函数,所以new只负责分配40字节的内存,将首地址赋给i,没有其他多于的操作,而string  *s=new string();不仅要分配string一个实例所需要的内存,还要调用string的default constructor,说这么多我只想说,构造函数不负责分配内存,只负责初始化,也就是说一个实例你想不想初始化,怎么初始化那是你程序员的事,跟编译器无关,编译器只在需要构造函数的时候,才会合成相应的构造函数,不需要的时候就不会合成。

先看一个简单的类:

class Point

{

public:

       int x;

       int y;

};

 

执行以下简单的代码:

int main() {

       {  

              Point *p=new Point;

              cout<<p->x<<endl;//第4行

              Point p1;

              //cout<<p1.x;//第6行

              p1.x=0;

              p1.y=0;

              Point p2(p1);//第9行

       }

        getchar();

     return 0;

}

第4行没有任何问题,x是个随机数,第6行就报错了,说x没有初始化,说明编译器没有自动合成default constructor构造函数,随机数就说明没有初始化啊,难道编译器会傻不啦叽的给你初始化成一个随机数,你看看随机函数的源码,你知道人家有多努力的帮你随机了,人家背地里默默地帮你做了很多事的,只能说明编译器没有帮你初始化,初始化工作就是程序员的事,至少C++是这样,C#编译器会帮你初始化。

第9行也不会导致产生copy constructor,因为这个类简单到实在没有必要合成复制构造函数,编译器完全可以在给p2分配完地址后,直接:Memcpy(&p2,&p1,sizeof(Point));不就是把一个地址的内容直接拷贝到另一个地址中去嘛,不用通够着函数的,构造函数还得一个的给字段赋值,多慢,在对象很简单的境况下,用memset进行初始化,memcpy进行赋值比给字段一个一个的赋值是要快一些的。说了这么多废话,那够着函数在什么情况下回产生呢?

一句话:在逐位拷贝解决不了问题的情况下,就得合成构造函数。说到这里其实大家差不多可以散了(只要你能深层次的理解这句话),但是我还有很多话要对你说。

需要产生default constructor的情况

1:成员变量含有default constructor

2:父类中含有default constructor

3:含有virtual function

4:继承关系中存在virtual继承

其实这4点概括一下就是两点:需要执行default constructor和实例中存在指向方法表的指针。在这4种情况下,仅仅是分配所需要的内存是不够的,前两种需要执行相应的default constructor,相应的代码当然是放在当前对象的默认构造函数中,后两种情况是因为指向方法表的指针需要初始化啊,哥,这个必须初始化啊,是编译器的职责啊,而字段是否初始化时程序员的职责。除了这4种情况,默认构造函数是不需要合成的,我都说了,构造函数不负责分配内存,编译器也不负责初始化,在没事的情况下编译器是不会多事的,如果程序员多事的加上了相关的构造函数,那绝对是手贱,你的代码很难快过编译器。

 

复制构造函数也是在需要的时候才合成,不需要的时候就不会合成,还是那句话,在逐位拷贝解决不了问题的情况下,就会合成复制构造函数,在里面做一些力所能及的事。

需要产生复制构造函数的情况:

1:成员变量含有copy constructor

2:父类含有copy constructor

3:含有virtual function

4:继承关系中含有虚继承

貌似产生的原因和default constructor差不多啊,还是要调用相应的构造函数,给指向方法表的指针赋值。如将一个子类对象赋给父类对象,就需要修改方法表的指向,因为之类和父类的方法表可能是不一样的啊,更多原因请看我的虚方法的调用是怎么实现的(单继承VS多继承)

构造函数是不会有事没事的产生的,不要再说那6个函数是一定会产生的了,只是在需要的时候才产生吗,还有一点让我受不了的就是关于初始化参数列表,疯狂的迷信初始化参数列表,导致初始化参数列表很长,当然用初始化参数列表是不会响应性能啊,而且有的成员变量只能在初始化参数列表中初始化,但是有的成员变量在初始化参数列表中初始化和在构造函数中初始化性能是一样的。那干嘛要把初始化参数列表搞得那么长呢?看着就蛋疼。

如果一个成员变量没有默认构造构造函数,且也不需要合成默认构造函数且可以不再初始化参数列表中初始化,那么他在初始化参数列表中初始化和在构造函数内初始化是一样的,原因很简单,构造函数不会针对这样的成员变量合成多于的代码,在哪里初始化都一样,但是成员变量如果有相关构造函数,编译器就会帮你调用,初始化参数列表中的代码会放到构造函数中,如果变量在初始化参数列表中初始化,就只初始化一次,而如果在够着函数中初始化,编译器看你没有在初始化参数列表中初始化,以为你没初始化,就产生代码帮你初始化了,而你又在构造函数中初始化那就初始化两次了,这就是为什么很多初学者认为初始化参数列表性能高的原因。

 

综上所述:在逐位拷贝解决不了问题时,编译器才合成相关的构造函数,执行相关的代码,在初始化的时候,看你没有初始化,而又有相关的构造函数,于是就帮你调用了,其他情况编译器概不负责,那是我们程序员自己的事。

posted @ 2013-08-25 16:41  啊汉  阅读(2411)  评论(3编辑  收藏  举报