构造函数产生的点及原因

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

构造函数不负责分配内存,只是在分配好的一块内存中进行赋值操作.这一点我们可以很容易从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个函数是一定会产生的了,只是在需要的时候才产生吗,还有一点让我受不了的就是关于初始化参数列表,疯狂的迷信初始化参数列表,导致初始化参数列表很长,当然用初始化参数列表是不会响应性能啊,而且有的成员变量只能在初始化参数列表中初始化,但是有的成员变量在初始化参数列表中初始化和在构造函数中初始化性能是一样的。那干嘛要把初始化参数列表搞得那么长呢?看着就蛋疼。

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

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

时间: 05-23

构造函数产生的点及原因的相关文章

字面量和构造函数

字面量和构造函数 JavaScript中的字面量模式更加简洁.有表现力,而且在定义对象时不容易出错.本章将会讨论字面量,包括对象.数组和正则表达式字面量,以及为什么字面量要比等价的内置构造函数(如Object().Array()等)要更好.本章还会介绍JSON格式,JSON是使用数组和对象字面量的形式定义的一种数据交换格式.本章还会讨论自定义构造函数,包括如何强制使用new以确保构造函数正确执行. 为了方便使用字面量而不是构造函数,本章还会补充一些知识,比如内置包装构造函数Number().St

关于C++ 中 thread 的拷贝构造函数

起因来自于<C++并发编程实战>的这样一个例子 #include <thread> #include <iostream> #include <stdexcept> class ScropeThread { public: ScropeThread(std::thread t) :m_pThead(std::move(t)) { if (!m_pThead.joinable()) { throw std::logic_error("no threa

C++中的链式操作

代码编译环境:Windows7 32bits+VS2012. 1.什么是链式操作 链式操作是利用运算符进行的连续运算(操作).它的特点是在一条语句中出现两个或者两个以上相同的操作符,如连续的赋值操作.连续的输入操作.连续的输出操作.连续的相加操作等都是链式操作的样例. 链式操一定涉及到结合律的问题.比如链式操作赋值操作满足右结合律,即a=b=c被解释成a=(b=c).而链式输出操作原则满足左结合律,即cout<<a<<b被解释成(cout<<a)<<b,基本

java学习笔记 --- 面向对象2

一.匿名对象 (1)没有名字的对象 (2)应用场景   A:调用方法,仅仅只调用一次的时候. class Car { //描述属性.颜色,轮胎数. String color; int number; //描述行为. void run() { System.out.println(color+":"+number); } } class CarDemo { public static void main(String[] args) { //采用匿名对象调用方法 new Car().ru

编写难于测试的代码的5种方式

本文由码农网 – 孙腾浩原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 有一次,我在一个讲座上听到主持人问听众如何故意编写难于测试的代码.在场的小伙伴都惊呆了,因为没有任何人会故意写这种糟糕的代码.我记得他们甚至给不出一个好的答案. 当然,这个问题的目的不在于教大家如何写使同事欲哭无泪的烂代码.而是为了了解什么样的代码难于测试,来避免这些严重的问题. 这里给出我对上面那个问题的答案(当然这只是我的个人观点,每个人讨厌的都不尽相同.) 1.用大量的静态字段 尤其是在不同类中共享静

JAVA初学者(一)

2015-12-15 21:26:17 刚学的java  做个总结: 1.构造函数没有返回值. 2.A对象调用Q的方法,Q方法里的变量就是A的变量 Fraction add(Fraction f) 在这个方法里体现的比较明显红色的fm就是调用者的fm这个构造函数里写this的原因就是与参数一个名字怕混淆 当然了 Fraction(int fz1,int fm)//构造方法没有返回类型,并且与类同名 { fz=fz1; if(fm==0) this.fm=1; else { this.fm=fm;

fastjson class泄漏

问题描述 fastjson在jetty容器中序列化HttpServletRequest会导致class泄漏,严重时会导致meta区溢出,导致无限FGC. fastjson序列化流程 fastjson通过动态生成代码提高序列化速度,序列化逻辑如下: String serialize(Object object) {     // 先看是否已经存在对应的serializer     Serializer serializer = serializerCache.get(object.getClass

【Head First Servlets and JSP】笔记7:如何创建一个全局的dog?

重定向与请求分派 “局部”参数——ServletConfig——servlet初始化参数 “全局”参数——ServletContext——上下文初始化参数 Web app的“构造器”——ServletContextListener 实战:如何创建一个全局的dog? 1.重定向与请求分派. resp.sendRedirect("http://www.cnblogs.com/xkxf/"); RequestDispatcher view = req.getRequestDispatcher

【C++】浅谈三大特性之一继承(三)

四,派生类的六个默认成员函数 在继承关系里,如果我们没有显示的定义这六个成员函数,则编译系统会在适合场合为我们自动合成. 继承关系中构造函数和析构函数的调用顺序: class B { public: B() { cout<<"B()"<<endl; } ~B() { cout<<"~B()"<<endl; } }; class D:public B { public: D() { cout<<"D