一、默认构造函数(Default Constructor)
初学者的两个错误认识:
如果一个类没有默认构造函数,编译器就为它自动创建一个构造函数;
编译器创建的默认构造函数会帮助我们将数据成员初始化或者置零。
第一部分将会解释这两个问题。先让我们来问几个问题:(1)什么是默认构造函数?(2)为什么编译器要自动创建默认构造函数?(3)什么时候编译器才为我们创建默认构造函数?(4)编译器创建的默认构造函数都干了什么工作?
OO中一个对象的建立编译器强制必须经过一个自我构造的过程,实现这个过程的函数就是构造函数。其中不带参数的那个就是默认构造函数。当我们的类不含任何构造函数的时候,编译器在某些情况下会自动创建一个默认的构造函数。那么这是为什么呢?在分类讨论之前首先记住一个准则:编译器创建默认构造函数仅仅是为了编译器自身的需要,并不是为了程序员的需要。这就决定了编译器并不总是像你想象的那样为你创建默认构造函数,而且在这个函数中做你假象要做的事情。
总的来说,编译器在下述四中情况下,会为一个没有任何构造函数的类创建默认构造函数。记住,只要你的类有了至少一个构造函数的话,编译器永远不会创建默认构造函数。但是编译器却会位你的构造函数穿插一些代码。呵呵,有点乱吧。别急,分情况讨论之。
情况1:一个类组合了一个带有默认构造函数的被组合对象
例子,见如下类声明。一个引擎Engine类有一个默认的构造函数,一个车Car对象则组合了一个Engine对象,但Car类没有默认构造函数。
{
public:
Engine();
} ;
Class Car
{
private:
Engine _engine; int _petrolLeft;
} ;
当编译器遇到这样的语句:Car car;或Car* pCar = new Car();的时候,编译器就需要一个默认的构造函数。这样编译器就在Car类中加入了下面几行:
这样编译器就可以利用这个默认构造函数来初始化Engine成员了。可以看到,这个函数是不会初始化_petrolLeft成员的,记住编译器只是为了自己的需要。如果你的类有了几个构造函数,那么编译器不会创建默认构造函数,而是在你的每一个构造函数的开始插入上述初始化Engine的代码。
Class Car {
private: Engine _engine; int _petrolLeft;
public:
inline Car() { _engine.Engine::Engine(); } // Added codes here.
};
情况2:从带有默认构造函数的基类继承
这种情况你可以根据上述思路自己思考一下。问题给你,什么时候编译器会创建构造函数?什么时候不创建?编译器会对构造函数做怎样的改动? Try to make it!
情况3:带有VPTR的类
如果你的类有了一个virtual的函数或是从某个具有虚函数的类继承下来或是含有多个虚继承的基类,那么如果你的类没有任何构造函数的话,编译器会根据多态的需要自动创建一个默认构造函数。举个例子。
class Vechicle {
private: Engine _engine;
public: virtual void Run() = 0;
};
class Car : public Vechicle {
private: Tyre _tyres[4];
public: virtual void Run() {......}
};
class Moto : public Vechicle {
private: Tyre _tyres[2];
public: virtual void Run() {......}
};
void Game::Run( Vechicle& vechicle )
{
vechicle.Run();
}
void Foo()
{
Game game; Car car; Moto moto;
game.Run( car );
game.Run( moto );
}
我们注意到,Game::Run方法中的vechicle.Run()方法需要迟绑定,也就是说需要在运行期确定对象的行为。这个行为的动态确定依靠的是改变VTable中Vechicle::Run的值来做到的。因此,编译器就要为Vechicle类族中的每一个类创建默认构造函数以便正确的修改VTable中的值。Game::Run方法可能就变成了这个样子:*vechicle.vptr[1](&vechicle);。
情况4:使用virtual继承的类
看个例子先。
这个pa->i是无法在编译器静态确定的,因此必须需要根据貌似pa->vbaseX->i来确定地址。所以编译器必须要构造好vbaseX这样的表以及指向这张表的指针,时机就在于默认构造函数。如果你编写的构造函数,编译器就在你的构造函数里面插入构造vbaseX的代码,必备在运行期使用。
好,关于默认构造函数就写到这里了。不懂的可以再看看《Inside The C++ Object Model》。有问题可以直接发问哦。
class X { public: int i; };
class A : public virtual X { ...... };
class B : public virtual X { ...... };
class C : public A, public B { ...... };
void Foo( const A* pa ) { pa->i = 10; }