构造函数语义学

一  Default constructor的建构操作

nontrivial default constructor的四种情况:

 1. “ 带有 Default Constructor ” 的 Member Class Object

class Foo 
{
    public:
        Foo();
        Foo(int);
}


class Bar
{
    public:
        Foo foo;
        char *str;
}

void foo_bar()
{
    Bar bar;  // bar必须初始化
             // Bar::foo是一个member object,而其class Foo拥有default constructor,符合条件
    if (str)
    ...
}

将 bar::foo 初始化是编译器的事情,但是将Bar::str初始化是程序员的事情,被合成的Bar的default constructor可能是这样的:

inline Bar::Bar()
{
    foo.Foo::Foo();
}

Bar::str并没有被初始化,所以我们需要自己为Bar写一个构造函数。

Bar::Bar()
{
    str = 0;
}

这样写的话 Bar::foo是否被初始化了呢,编译器的行动是:“ 如果编译器 A内含一个或一个以上的member class objects, 那么 class A的每一个构造函数必须调用每一个member classes 的default constructor”,编译器会扩张已有的构造函数,在其中安插一些代码,使得在调用 user code 之前,先调用必要的 member classes 的default constructor 。所以扩张后的代码可能是这样的:

Bar::bar()
{
    foo.Foo::Foo();   // 附加上的compiler code
    str = 0;          // explicit user code 
}

如果有多个 class member objects 都要求constructor 初始化操作,则以 “ member objects在class中的声明次序 ” 来调用各个constructor。

所以有 class member objects 的时候千万别忘了自己定义constructor,给非class member objects(即数据成员)初始化!!!

 

2. “ 带有Default Constructor ” 的 base class 

如果 derived class 没有带有default constructor 派生自 带有 “ default constructor ” 的 base class, 那么这个 derived class 的default constructor 需要被合成出来。 它将调用上一层 base class 的 default constructor。

如果这个 derived class 有多个 constructor ,但是并没有 default constructor,编译器会扩张每一个constructor,将必要调用的 default constructor 加入进去,它不会合成一个新的 default constructor  

 

3. “ 带有一个 Virtual Function ” 的 Class

另有两张情况,需要合成 default constructor :

1. class 声明(或继承)一个 virtual function。

2. class 继承自一个派生链,其中有一个或更多的 virtual base function。

期间编译器会生成 vtbl 和 vptl,vptl 指向 vtbl 。

 

4. “ 带有一个 Virtual Base Class ” 的 Class

必须使 virtual base class 在其每一个 derived class object 中的位置,能够于执行期间准备妥当。

class X 
{
    public:
        int i;
};

class A : public virtual X
{
    public:
        int j;
};

class B : public virtual X
{
    public:
        double d;
};

class C : public A, public B
{
    public:
        int k;
};

// 无法在编译时期决定 pa->X::i 的位置
void foo(const A* pa)
{
    pa->i = 1024;
}

main()
{
    foo(new A);
    foo(new C);
}

编译器无法固定住foo()之中 “ 经由 pa 而存取的 X::i ” 的实际偏移位置,因为 pa 的真正类别可以改变,编译器必须等到执行期才能确定下来。原先的cfront的做法是靠在 derived class object的每一个virtual base class 中安插一个指针完成。所有经由 “ reference ” 或  “ pointer ” 来存取一个 virtual base class 的操作都可以通过相关指针完成。

foo() 可以改下成以下

void foo(const A* pa)
{
    pa->_vbcx->i = 1024;  
}

其中 _vbcx 是编译器产生的指针,指向 virtual base class X。_vbcx 是在 class object 建构期间完成的。对于class所定义的每一个 constructor,编译器会安插那些 “ 允许每一个 virtual base class 的执行期存取操作 ” 的码。如果 class 没有任何 constructor,编译器会为它合成一个 default constructor。

总结 :

 

二  Copy Constructor的建构操作

有三种情况,会以一个 object 的内容作为另一个 class object 的初值。

1. 最明显的一种情况就是对一个 object 做明确的初始化操作:

class X
{
    。。。;
}

X x;
X xx = x;

另两种情况是当 object 被当做参数交给某个函数时,例如:

external void foo(X x);

void bar()
{
    X xx;
    // 以 xx 作为 foo() 第一个参数的初值(不明显的初始化操作)
    foo(xx);
}

以及当函数传回一个 class object 时,例如 :

X foo()
{
    X xx;
    return xx;
}

那么在大部分情况下,当一个 class object 以另一个同类实体作为初值时,上述的 copy constructor 会被调用,这可能会导致一个暂时性 class object 的产生或程序代码的蜕变(或两者都有)。

 

Default Memberwise Initialization   

假如 class 没有提供一个 explicit copy constructor,其内部是以 default memberwise initialization 的手法完成的,也就是每一个内建的或派生的 data member (例如一个指针或者一个数组)的值,从某个 object 拷贝一份到另一个 object 上,不过它并不会拷贝其中的 member class object,而是以递归的形式施行 memberwise initialization。例如:

未完待续...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值