这一章节我们来了解下什么时候编译器既会声明也会定义默认构造函数。它也同样解释了为什么看起来没有什么意义的即使不给定义也要声明一个默认构造函数的意义。
Q:编译器什么时候隐式定义一个默认构造函数。
答: 一个隐式声明的默认构造函数只有在编译器需要的时候才隐式定义一个默认构造函数。只有如下情况其才是必须的。
1、拥有虚成员函数的类。一个有虚成员函数的类一定会有一个non-trivial的构造函数。一个non-trivial的构造函数可能是用户定义的或者是编译器隐式定义的构造函数。并且它有责任为例如vptr的地址提供正确的初始化等。
2、带有Default Constructor 的 Member Class Object
3、带有Default Constructor的Base Class
4、带有一个Virtual Bass Class 的 Class
以上详情请见(深度探索C++对象模型)
Q:为什么编译器隐式声明Trivial构造函数即使他们从未定义?
答:在一定条件下,编译器隐式声明一个默认构造函数但并不定义它。这样的构造函数就是所谓的trivial。很多c++程序员对trivial成员函数的概念感到非常困惑。
当编译器知道某个函数不会被默认定义时究竟是为什么这个编译器还要隐式的声明一个默认的构造函数呢?做这些有什么目的么?
首先第一件要牢记心中的事是隐式声明是概念上的,编译器并不会真正的在你的代码里面插入所谓的声明代码。更准确的说应该是编译器、连接器和程序表现的就像构造函数已经声明了。实际上,编译器只是简单的在类的类型信息记录里面设置了一些bits用来表明它们所谓的类型有一个默认构造函数的声明。
现在我们回到最关键的问题上来吧,为什么要麻烦的有个隐式声明?毕竟C并不需要为它的struct或union提供这样的机制。事实是隐式声明有一个契约似的规则。每一个隐式声明就像合同里面的条款声明了一个确定的类怎么被使用。当编译器隐式的声明这个特殊的成员函数,它授予某些授权给用户,相比之下,如果编译器不隐式声明成员函数,那就限制了用户对类的使用。考虑下面的代码:
struct Blocked
{
public:
Blocked(const Blocked&);
};
现在你肯定知道,因为copy-constructor的存在让编译器并不会隐式声明。因为这个类没有隐式或显示的声明默认构造函数,你并不能像下面一样实例化一个类成员。
Blokced b; //error, no default constructor available
Blocked *p = new Blocked; //error
没有隐式声明的机制,程序员不得不为每一个需要实例化的类手动的添加构造函数,拷贝构造函数,赋值函数和析构函数。下面的POD类型证明了这个观点:
struct Date
{
int day, month, year;
};
编译器饮食声明了如下成员函数:
- A trivial default constructor
- A trivial copy constructor
- A trivial assignment operator
- A trivial destructor
Date d; //implicit declaration of default ctor and dtor allow this
Date * pdate= new Date; //same here
Date d2(d); //implicit copy ctor declaration allows this
*pdate=d2; //implicit assignment operator declaration allows this
delete pdate; //implicit dtor declaration allows this
trivial成员函数在C++编程里面是非常重要的,为了让你明白它能为你省去多少麻烦,试着去显示声明它们:
struct Date
{
int day, month, year;
private:
~Date(); //declared but not defined
Date(const Date&); //ditto
};
Date d; //error, no default ctor
Date * pdate= new Date; //same error here
Date d2(d); //error, no accessible copy ctor
现在上面的代码就不能通过编译了。