您的位置:首页 > 编程语言 > C语言/C++

深入理解C++对象模型之构造函数

2017-04-24 11:21 267 查看
一、前言

    学习C++的同学一般都知道有构造函数这个东西,我相信很多同学的理解就是构造函数是用来初始化类成员的,是的,构造函数的本质确实是这样的,但很多同学会有以下两个误解:

        (1)任何class如果没有定义任何构造函数,编译器就会帮你自动生成一个;

        (2)编译器用合成出来的默认构造函数会“Class内的每一个[b]data member”;[/b]

先不说这两个观点对不对,但至少他不严谨。

二、构造函数与默认构造函数

    这里首先引出C++Primer里面的关于构造函数的介绍:构造函数的任务是初始化类对象的数据成员,无论什么时候只要class的对象被创建,就会执行构造函数。我的理解是这里所说的构造函数是User Constructor Function,即用户定义的构造函数,与其相对应的还有一种叫做默认构造函数。在网上看到关于默认构造函数的定义为:可以不用实参进行调用的构造函数,其包括两种情况:(1)没有带明显形参的构造函数;(2)提供了默认实参的构造函数。

    这里之所以要介绍默认构造函数,是因为用户可以自己定义一个默认构造函数,而编译器也可以为我们合成一个默认构造函数,其实编译器合成的构造函数确实是都不带明显形参的,因此我们经常把编译器合成的构造函数称为“合成默认构造函数”

三、编译器需要的构造函数和程序需要的构造函数

    用户定义一个以下类和它的构造函数

class A
{
public:

private:
int a;
int b;
};

class A没有定义构造函数,按照前言中,大多数同学的第一个误解,即任何class如果没有定义默认构造函数,编译器就会帮你自动生成一个,OK,那么生成一个默认构造函数干什么呢?同学的潜意识里是要求编译器用生成的这个构造函数去初始化成员变量a和b。那这么说是不是就能用这个类去实例化一个对象呢?因为只有实例化对象的时候才需要调用构造函数啊,这才是你自以为的编译器合成的构造函数的用武之地啊。下面在VS2010IDE中进行验证,结果是这样的:

#include <iostream>
using namespace std;
class A
{
public:

public:
int a;
int b;

};

void main()
{
A obj1;
cout<<"obj1.a = "<<obj1.a<<endl;
}




    What?为什么结果是这样的呢?对象obj1没有被初始化?不是说编译器会生成一个默认构造函数来初始化他的成员变量吗?其实实际上编译器并没有合成默认构造函数,因为你所谓的编译器会合成一个构造函数来初始化其成员变量,那是你认为,自以为的,并不是编译器以为的,也就不是编译器需要的,而是程序的本身需要,你要访问对象obj1的成员,程序当然需要先对其初始化,但是程序需要并不等于编译器需要

    因此,这就可以推翻同学们的第一个误解,很多时候编译器并没有帮你生成默认构造函数,即使你没有定义任何构造函数,因为这不是编译器的必须工作(尽管编译器背着你干了很多有利于你的事情,但它也不是傻子,不是它的事情,它肯定不会干)

    那既然编译器不会帮你合成默认构造函数,那为什么有很多人这样说呢?我想这应该是断章取义的结果。在《深入理解C++对象模型》中,侯捷大师引用C++ standard很明确的给了我们答案。第一个说明是:对于Class X,如果没有任何用户声明的构造函数,那么会有一个默认构造函数被隐式声明出来,……一个被隐式声明的默认构造函数是trivial(浅薄无能的、没啥用的)构造函数。这就和同学的第一点误解有很大的相似性,但仅仅是相似,而完全不同,因为这里说的只是声明出一个trivial的构造函数,并不是为误解了的会合成出一个默认构造函数,声明了并不代表一定要合成(即定义)

    那么是不是都不会合成呢?也不是,候老师给的解答是:默认构造函数只有在需要的时候被编译器产生(合成、定义)出来。关键字眼是“在需要的时候”,被谁需要?那当然是被编译器需要,那什么情况下编译器才是需要的呢?就是下面四种情况:

    (1)带有默认构造函数的类成员对象。即如果一个Class A没有定义任何构造函数,但它含有一个Class B对象成员,而Class B有默认构造函数,那么Class A的默认构造函数就是被编译器需要的,因此编译会合成Class A的默认构造函数。这里Class A和B分别定义如下

class B
{
public:
B()
{
m=1;
n=2;
}
private:
int m;
int n;
};
class A
{
public:
public:
    int a;
    int b;
B obj2;
};
现在再访问Class A的对象,如

void main()
{
A obj1;
cout<<obj1.a<<endl;
}
不会再出现Class obj1未定义的错误,但是输出的结果为未知数,比如我的运行结果是:a=-858999460;这是一个未定义的整数,为什么呢?程序没有报错,说明Class A的对象obj1已经正确被构造,也说明编译器已经为我们合成了默认构造函数,初始化了Class B的对象obj2,但为什么没有初始化自己的成员对象a和b呢?还是那句话,编译器只做自己该做的事情,不是它的事情它不会做,而成员变量初始化时程序需要的,则应该有程序员来完成,就算编译器合成了一个有用的默认构造函数,它也不会初始化变量a和b,它只负责调用Class
B的默认构造函数来初始化Class B的对象obj2
。如下面的程序执行

void main()
{
A obj1;
cout<<"obj1.a = "<<obj1.a<<endl;
cout<<"obj2.m = "<<obj1.obj2.m<<endl;
}
结果为:(能充分说明上述的分析)



上述结论,也能推翻同学们对前言中的第二个误解,编译器用合成出来的默认构造函数会“Class内的每一个data member”,其实编译器它只做自己该做的事情。

    (2)带有默认构造函数的基类。如果一个没有任何构造函数的Class是继承于一个带有默认构造函数的基类,那么派生类的默认构造函数也是被编译器需要的,编译器需要为用户合成一个默认构造函数。

        因为构造子类之前需要先构造基类,即使派生类没有定义任何构造函数,编译器也会合成一个默认构造函数,用来调用基类的默认构造函数。

    (3)带有虚函数的Class。即如果一个Class定义了虚函数,那么编译器也要合成默认构造函数。

    (4)虚拟继承体系中的派生类,编译器也需要合成默认构造函数。

        对于情况(3)和(4),编译器之所以要生成默认构造函数,是因为编译器需要在合成的默认构造函数中对虚函数和虚拟继承机制进行支持,即需要设定或者重置虚函数表或者虚基类指针。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息