C++中的空类有默认产生哪些户成员信息空的函数函数

  在C++中当创建一个空类时,C++就会默认的为这个类创建4个函数:默认的构造函数、析构函数、拷贝构造函数、以及赋值操作符。本文参考Effective C++介绍这几个函数。


1. 函数的原型以及函数创建的时机

C++中创建一个空类:

默认会生成4个函数,其函数的原型如下:

说明:1) 这些函数只有在需要调用的时候,编译器才会生成。2) 4个函数都是public的。3) 4个函数都是inline的(即函数定义在类的定义中的函数)。4) 如果你显式的声明了这些函数中的任何一个函数,那么编译器将不再生成默认的函数。

比如,当遇到下列语句时,函数会被编译器生成:

//对象销毁时,析构函数

另外,还存在两种默认的函数:就是取地址运算符和取地址运算符的const版本,这两个函数在《Effective C++》中没有提及。

这两个函数是确实存在的,正如下面的代码可以正常工作:

一个容易被忽略的问题:自定义的拷贝构造函数不仅会覆盖默认的拷贝构造函数,也会覆盖默认的构造函数。下面的代码是编译不过的,用户必须再显式的定义一个无参的构造函数。

2.  赋值操作符存在的问题

赋值操作符函数的行为与拷贝构造函数的行为基本是相同的,编译器生成赋值操作符函数是有条件的,如果会产生无法完成的操作,编译器将拒绝产生这一函数。那么什么时候编译器无法完成赋值这一行为呢?考虑如下情形(来源Effective C++):

 然后考虑下面的语句会发生什么事:

因此如果上面两种情形中的任何一种发生了,C++编译器给出的响应是:拒绝编译这一行的赋值动作。如果你这么做了,C++编译器会报错。如果你执意要进行赋值操作,那么可以自己定义一个赋值操作符重载函数。

C++0x中引入了“右值引用”和“移动语义”的概念,可以实现对右值的引用。(左值和右值的解释可以见)

移动语义,简单来说,就是在一个右值对象的生命期结束之前,将其资源偷过来,为我所用。有关移动语义的详细内容这里不做详述,大家可以参见csdn上一篇文章 。这里要说明的是移动构造函数和移动赋值运算符。

1. 移动构造函数和移动赋值运算符重载函数不会隐式声明,必须自己定义。

2. 如果用户自定义了拷贝构造函数或者移动构造函数,那么默认的构造函数将不会隐式定义,如果用户需要,也需要显式的定义

3. 移动构造函数不会覆盖隐式的拷贝构造函数。

4. 移动赋值运算符重载函数不会覆盖隐式的赋值运算符重载函数。

定义一个空的C++类,例如

一个空的class在C++编译器处理过后就不再为空,编译器会自动地为我们声明一些member function,一般编译过去就相当于

一般的书上好像都是前面四种:默认构造函数,拷贝构造函数,默认赋值函数以及析构函数,后面两种其实属于,但要需要注意的是,只有当你需要用到这些函数的时候,编译器才会去定义它们。

如果你只是声明一个空类,不做任何事情的话,编译器会自动为你生成一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操作符和一个默认析构函数。这些函数只有在第一次被调用时,才会别编译器创建。所有这些函数都是inline和public的。

        在这里,我想讨论两个问题,第一问题是这几个默认定义的函数里面有无虚拟函数,第二个问题是拷贝构造函数和赋值操作符函数的缺陷所在。

1. 默认定义的函数里面有无虚拟函数?

        先补充一个知识点,定义了虚函数的类在分配内存时要多出4个字节的空间来存储指向虚函数表头的地址,可参见。

构造函数不存在虚拟这一说法。析构函数在我们平时用的时候一般都会将其定义为虚拟的,原因就是保证在将创建的子类对象空间赋给父类指针时,在使用delete方式释放该父类指针指向的空间时,能够先调用子类的析构函数来释放资源;如果我们把析构函数定义为非虚拟的,那么在上面这种情况下,只会去调用父类的析构函数。那么基于这种原因,按理来说,c++编译器应该在默认定义析构函数时将其定义为虚拟的。而c++编译器并没有这么做,原因可能就是它无法预知开发人员在定义类时会赋予类什么样的行为,而且定义虚方法会为类空间多增加4个字节的内存PS: 这只是我的猜测,具体原因不详。)。

        将赋值操作符函数定义为虚拟的是没有意义的!为什么这么说,下面我举个例子:

执行的是Son::operator=方法,如果执行的是此方法,那么第二个打印语句打印的数据应该为11,如果执行的方法是Parent::operator=方法,那么打印的数据应该为10。看下VS运行的结果:


这个结果表明调用的是Parent::operator=方法,其实这个语句的另一个写法就是:

这是为什么呢?C++类的转换有两种,一种是向上转换,即将子类型转换为父类型,这种转换可以隐式直接转换即可;另一种是向下转换,即将父类型转换为子类型,这种转换需要使用dynamic_cast语法进行转换。如果上面的语句是调用的Son::operator=方法,那么可以继续将这条语句补齐:

所以将赋值操作符函数定义为虚拟的是没有意义的,最终还是需要转换为子类型来调用子类中重载的赋值操作符函数,将这个结果衍生一下就是,将任何带有以当前子类作为参数的函数定义为虚拟的是没有意义的,就比如operator==,operator+等等。所以上面的代码应改为:

。声明或继承了虚函数的类叫做多态类。可参见

另外关于一个在类的哪一层定义虚方法的问题,就比如析构函数,最好是在基类就定义析构函数为虚函数,函数的虚拟性是存在继承的特征的,这样在子类中即使不显示将析构函数定义为虚拟的,它还是虚拟函数。这个函数虚拟的继承特性在普通的函数重载中也是一样的,在父类中定义了一个普通的虚拟函数,那么在子类中重实现这个函数时,即使不显示将其定义为虚拟的,那它还是虚拟的

2. 拷贝构造函数和赋值操作符函数的缺陷

        在实际的工程编码当中,默认的拷贝构造函数和赋值操作符函数会给你带来很多的麻烦!!

  默认的拷贝构造函数和赋值操作符函数均是采用的位拷贝(也称“浅拷贝”)方式来给类的属性变量进行赋值。拷贝方式分两种,位拷贝(也称“浅拷贝”)和值拷贝(也称“深拷贝”),位拷贝是指将一个对象的内存映像原封不动地赋值给另一个对象,值拷贝则是将一个对象的内存映像所指向的值赋值给另一个对象。编译器默认采用了一种共享的方式来定义拷贝的行为,而实际上编译器也只能做到如此,但是这样一个特性饱受诟病,那为何又要保留这个特性呢?C++语言之父在他写的《The

        首先这个特性是继承自C语言的,其主要的一个目的主要是为了与C语言兼容。这个特性既是C++的一个大的缺陷,也正式C++流形的一个主要原因。

        使用面向对象,大家就一定会使用设计模式,而设计模式可分为两大类:继承的体系结构,聚合/组合的体系结构。在使用第二类设计模式时,相信大家都很能体会到这个特性的缺陷,为了防止发生浅拷贝,我们“必须”去重载拷贝构造函数和赋值操作符函数。而除了重载这两个默认函数外,另外一个途径就是禁用这两个函数,将这两个函数定义为私有的访问类型。

我要回帖

更多关于 空成员 的文章

 

随机推荐