Shell's Home

May 2, 2008 - 3 minute read - Comments

C++继承,虚,转换规则探究

以下讨论的东西都是在VS2005下跑出来的,如果想知道别的编译器规则,请照跑一遍。以下是类定义,函数内容为打印出当前函数名称,所以就不再贴了。

class Base
{
public:
    Base();
    Base(const Base & o);
    virtual ~Base();
    virtual Base & operator = (const Base & o);

    void function1();
    virtual void function2();
    void function3();
    virtual void function4();
    //virtual void function5();
    virtual void function6();
};
class Derive : public Base
{
public:
    Derive();
    Derive(const Derive & o);
    virtual ~Derive();
    virtual Derive & operator = (const Derive & o);

    void function1();
    virtual void function2();
    virtual void function3();
    void function4();
    //compiler error
    //int function5();
protected:
    virtual void function6();
public:
};

首先我们讨论继承下的构造/析构顺序。

pa = dynamic_cast(new Derive ());
delete pa;
Base::Base
Derive::Derive
Derive::~Derive
Base::~Base

关于这段代码多说两句,如果我们把class Derive : public Base中的public删除,就会出现C2243错误,看来默认是私有继承。

先是基类构造,然后是继承类构造。先是继承类析够,然后是基类析够。然后我们将virtual ~Base();的virtual删除,结果就变成了。

Base::Base
Derive::Derive
Base::~Base

注意继承类的析构没了。所以如果你打算让人继承你的类,记得将类的析构改成virtual,否则他怎么写析构都不会被调用的。

然后是虚函数继承。

pa->function1 ();
pa->function2 ();
pa->function3 ();
pa->function4 ();

结果是这样。

Base::function1
Derive::function2
Base::function3
Derive::function4
Derive::function6

看来,虚特性出来不出来完全看基类。注意到上面的function5么?假设你继承了一个类,打算写一个函数,和基类里面的某个虚函数具有一样的名称和参数,但是返回不一样。嘟嘟~~抱歉,编译器错误。而且注意function6,即使在继承类中声明说这是保护函数,也可以通过公开的基类函数的虚特性进行调用。

下面我们要说一下拷贝构造函数,这不可避免的要说到定义。

Derive::Derive(const Derive & o)
{
    printf ("Derive::Derive copy constructern");
}

猜猜这个会出什么结果?

Base::Base
Derive::Derive copy constructer

要是经常看我blog的人就不会意外,继承类的拷贝构造函数调用的是基类的普通构造函数。如果你打算让基类也拷贝构造,那这么做。

Derive::Derive(const Derive & o):Base (o)
{
    printf ("Derive::Derive copy constructern");
}

然后是拷贝构造函数的使用时机。运行代码如下,我们逐步分析。

Base ta = *pa;
Base::Base copy constructer
Base::~Base

当对象声明时,如果加一个=,则以=后的对象来构造当前对象,这是拷贝构造的第一个用法。

Derive tb = *static_cast(pa);
Base::Base copy constructer
Derive::Derive copy constructer
Derive::~Derive
Base::~Base

当然,如果我们声明继承类的时候,一样拷贝构造。

//compiler error
//Derive tc = ta;

当我们试图用基类构造继承类的时候,理所当然的,出错了。

void test1 (Base &)
{
    printf ("test1n");
}
test1(*pa);

输出:test1

如果我们以一个对象调用的时候,如果是引用,当然是不拷贝的。

void test2 (Base)
{
    printf ("test2n");
}
test2(*pa);

Base::Base copy constructer
test2
Base::~Base

如果是直接调用,首先是拷贝构造,然后调用,最后析构。

Base& test3 ()
{
    printf ("test3n");
    return Base ();
}
pb = &test3();

test3
Base::Base
Base::~Base

当返回对象引用的时候,只有很正常的构造和析构。

Base test4 ()
{
    printf ("test4n");
    return Base ();
}
pb = &test4();

test4
Base::Base
Base::~Base

返回对象本身的话,哎,怎么会这样?

熟悉语言的应该看出来了,return Base ();的时候,先跑了一次构造,建立在栈里面,返回的时候要copy到堆中。拷贝构造呢?

这就是传说中的返回构造优化拉,直接构造在堆上面,省掉一次copy,下面我们看看原始的状态。

Base& test5 ()
{
    Base b;
    printf ("test5n");
    return b;
}
pb = &test5();
Base test6 ()
{
    Base b;
    printf ("test6n");
    return b;
}
pb = &test6();

Base::Base
test5
Base::~Base
Base::Base
test6
Base::Base copy constructer
Base::~Base
Base::~Base

大家看到了?5的时候先构造,再传回,和返回对象引用的时候行为一致。6的时候可没有返回构造优化,于是先构造,然后拷贝。删除的时候先删除原始对象,再删除拷贝对象,大家可以自行证实这点。

我们再修改上面的调用为下面的。

Base td = test5();
Base::Base
test5
Base::~Base
Base::Base copy constructer
Base::~Base

首先是5的构造,析构,然后才是td的拷贝构造,析构。这个顺序,熟悉语言的人应该感觉到奇怪了吧。按照推论,应当是先拷贝再析构的。如果你这么觉得,还是先看完下面的东西吧。

Base te = test6();
Base::Base
test6
Base::Base copy constructer
Base::~Base
Base::~Base

这才是预计的顺序。注意,这里并没有调用两次拷贝构造。虽然贝壳并不了解机制,不过估计又是一种返回构造优化。

5中例子觉得迷惑的人,不妨在拷贝构造里面打个断点,看看你copy的对象是什么,无效对象!!!!

返回引用的情况下,一旦返回对象的生命周期结束了,返回的数据就无法保证有效。因此返回局部对象是非常危险的,唯一的里外就是3例子中在返回的时候构造一个新的对象而引发的返回构造优化。

下面是拷贝构造和operator =的区别和调用时间。

Base ya = *pa;
Base yb;
yb = *pa;
Base::Base copy constructer
Base::Base
Base::operator =
Base::~Base
Base::~Base

上面一个是拷贝构造,下面一个是普通构造加operator =。

最后是全部的定义和源码,类的定义参考最上面的。

void test1 (Base &)
{
    printf ("test1n");
}
void test2 (Base)
{
    printf ("test2n");
}
Base& test3 ()
{
    printf ("test3n");
    return Base ();
}
Base test4 ()
{
    printf ("test4n");
    return Base ();
}
Base& test5 ()
{
    Base b;
    printf ("test5n");
    return b;
}
Base test6 ()
{
    Base b;
    printf ("test6n");
    return b;
}
int _tmain(int argc, _TCHAR* argv[])
{
    Base *pa, *pb;

    pa = dynamic_cast(new Derive ());

// test inherit function rule
//pa->function1 ();
//pa->function2 ();
//pa->function3 ();
//pa->function4 ();
//pa->function6 ();

//test copy constructer
//pb = dynamic_cast(new Derive (*static_cast(pa)));
//delete pb;
//Base ta = *pa;
//Derive tb = *static_cast(pa);
//compiler error
//Derive tc = ta;
//test1(*pa);
//test2(*pa);
//pb = &test3();
//pb = &test4();
//pb = &test5();
//pb = &test6();
//Base td = test5();
//Base te = test6();

//diffrence between copy cotr and operator =
//Base ya = *pa;
//Base yb;
//yb = *pa;

    delete pa;
    return 0;
}