C++:对象切片及拷贝构造函数 RTTI

C++提供了继承机制和虚拟,并通过(且只能通过)指向同一类族的指针或者引用来实现多态,否则多态不起作用。原因之一就是这里要说一下的著名的对象切片(Object slicing)问题。

无虚拟机制的继承的切片问题
首先,类中毫无疑问地需要有继承和虚拟。没有这两者,就不存在多态(注意,重载并不属于多态——个人理解,欢迎来搞)。由于虚拟机制的复杂性,先用一个小例子来说明一下只有继承时的切片问题。假定有两个类:
class MyBase
{
public:
    void Get(){};
    void Set(){};
public:
    int b;
};

class DerivedMyBase: public MyBase
{
public:
    void Print(){};
    void GetD(){};
};
如果有下面的语句:
DerivedMyBase aDMB;
MyBase aMB = aDMB;

那么,通过aMB来访问d或者Print()就是非法的:
// Illegal to access GetD() or Print() through aMB
aMB.GetD();
aMB.Print();

这是因为在将aDMB拷贝给aMB时发生了对象切片,在aMB对象中只有MyBase的信息,所有的关于DerivedMyBase类的信息都被切片了。在“MyBase aMB = aDMB;”还涉及到默认拷贝构造函数的问题,下文会详细描述。

这仅仅是最简单的一种情况。要注意区分下面这种情况:
DerivedMyBase aDMB;
MyBase * pMB = &aDMB;

通过pMB来访问d或者Print()仍然是非法的:
// Illegal to access GetD() or Print() through pMB
pMB->GetD(); // Of course one can use dynamic_cast<> to make this call legal.
pMB->Print();

由于没有虚拟机制,多态在这里仍然不起作用,然而,这里并没有对象切片的发生。因为DerivedMyBase是一个MyBase,所以“MyBase * pMB = &aDMB;”是合法的。而pMB仅仅是一个指针,通过该指针引用的是aDMB,但编译器对于该指针应用对象的了解仅限于MyBase,对于DerivedMyBase类的信息一无所知——这也就是在实践中通常将基类作为抽象类来实现多态的原因,此时派生类中的所有不属于基类的信息都无法通过基类指针或引用来获取,因为编译器在解析该指针或引用指向的内存区时是按照基类的信息来解释的。

对象切片的机理
那么,对象切片是如何发生的?简而言之,是由compiler向拷贝构造函数中插入的代码来做。由于在“MyBase aMB = aDMB;”中由编译器生成的拷贝构造函数不需要对虚拟机制进行额外的处理,此时依照bitwise copy,所有属于DerivedMyBase的信息都丢掉了。而在“ MyBase * pMB = &aDMB;”中,根本就不需要调用copy ctor,所以切片不会发生。

下面,为MyBase和DerivedMyBase加入虚拟机制,看看情况有什么变化:
class MyBase
{
public:
    virtual void Get(){};
    virtual void Set(){};
public:
    int b;
};

class DerivedMyBase: public MyBase
{
public:
    void Print(){};
    void GetD(){};
};

首先编译器会在你的ctor或者编译器为你生成的ctor中加入对虚拟机制的处理代码,这也使得默认拷贝构造函数及对象切片问题变得异常复杂。——此处虚拟机制包括virtual函数和virtual基类。

memberwise copy和bitwise copy
首先说一下深拷贝(memberwise copy)和浅拷贝(bitwise copy)的问题。一般来说,自己定义的copy ctor对于对象的拷贝会有严格的、符合语义的定义(人为错误、破坏因素除外)。然而,无论是自定义的还是默认的ctor,编译器都会插入对虚拟机制的处理代码,这就保证对象切片和拷贝正确的发生——可能会出乎你的意料,但符合C++的语法语义。

虚拟机制与拷贝方式
当类中没有虚拟机制、没有其他类对象的成员时(只包含built-in类型、指针或者数组),默认copy ctor进行的是bitwise copy,这会导致对象切片的发生。然而,当类中有虚拟机制,或者有其他类对象成员时,默认copy ctor采用的是memberwise copy,并且会对虚拟机制进行正确的拷贝。

因为包含虚拟机制的类在定义一个对象时,编译器会向ctor中添加初始化vtable和vbaseclasstable(依赖于具体编译器)的代码,这样可以保证vtable中的内容与类型完全匹配。也就是说MyBase和DerivedMyBase有这相似的VTABLE,但不是完全相同——例如DerivedMyBase中还可以定义自己的virtual函数,这样它的VTABLE就会有更多表项。

而多态的实现是通过将函数调用解析为VTABLE中的偏移量来实现。pMB->Get()可能会被编译器解析成:
(*pMB->__vtable[Offset_of_Get])();

而当MyBase作为虚基类时,访问其中的数据成员可能就是:
pMB->__vBaseClassMyBase->b;

那么,当“aMB = aDMB;”,copy ctor会执行memberwise copy,正确的初始化aMB的VTABLE,而不是仅仅将aDMB的VTABLE拷贝过来。如果是bitwise copy,aMB对象中的VTABLE将是aDMB的,aMB.Get()调用的将是DervieMyBase定义的Get(),这显然是不符合语义和逻辑的。

总而言之
对象切片和copy ctor是一个很复杂的东西,在有虚拟机制的情况下两者是紧密结合在一起的。因为对象切片和拷贝构造函数的问题,不通过指针或者引用无法达到多态的目的。

还有一个问题是赋值拷贝的问题,这个机制更复杂,因此Lippman建议不要再虚基类中使用数据成员。C#和java禁止了多重继承,并将interface作为一个单独的东西,消除了赋值拷贝带来的复杂性。关于赋值拷贝的问题,有机会再讨论。

PS:上述代码均能由g++ 3.4.4编译。对于C++的复杂性,想必很多人都有切身感受。
顺祝ChinaUnix的所有朋友们新年快乐,万事大吉,新年发大财!

参考:
Inside the C++ Object Model, by Stanley B Lippman.

Copyleft (C) 2007-2009 raof01.
本文可以用于除商业外的所有用途。此处“用途”包括(但不限于)拷贝/翻译(部分或全部),不包括根据本文描述来产生代码及思想。若用于非商业,请保留此权利声明,并标明文章原始地址和作者信息;若要用于商业,请与作者联系(raof01@gmail.com),否则作者将使用法律来保证权利。

以上转自:http://hi.baidu.com/beege/blog/item/c9c170df65835e1a495403c2.html

 

 

RTTI与虚函数表
在C++ 程序中﹐若类含有虚函数﹐则该类会有个虚函数表(Virtual Function Table﹐ 简称VFT )。为了提供RTTI﹐C++ 就将在VFT 中附加个指针﹐指向typeinfo对象﹐这对象内含RTTI资料,如下图:
         由于该类所实例化之各对象﹐皆含有个指针指向VFT 表﹐因之各对象皆可取出typeinfo对象而得到RTTI。例如﹐
                Figure *f1 = new Square();
                Figure *f2 = new Square();
                const typeinfo ty = typeid(*f2);
其中﹐typeid(*f2) 的动作是﹕
1.取得f2所指之对象。
2.从对象取出指向VMF 之指针﹐经由此指针取得VFT 表。
3.从表中找出指向typeinfo对象之指针﹐经由此指针取得typeinfo对象。
        这typeinfo对象就含有RTTI了。参考下图1,经由f1及f2两指针皆可取得typeinfo对象﹐所以         typeid(*f2) == typeid(*f1)。
转自:http://hi.baidu.com/xun1573/blog/item/d7a555d3e4fdb8053bf3cf51.html

 

运行时开销恐怕是程序员最关心的问题之一了。相对与传统C程序而言,C++中有可能引入额外运行时开销的新特性包括:

  1. 虚基类
  2. 虚函数
  3. RTTI(dynamic_cast和typeid)
  4. 异常
  5. 对象的构造和析构

转自:http://baiy.cn/doc/cpp/inside_rtti.htm

 

什么时候一个class不展现出“bitwise copy semantics”呢?有四种情况:

1.当class内含一个member object而后者的class声明有一个copy constructor时(不论昌被class设计者明确声明或是被编译器合成)。
2.当class继承自一个base class而后者存在一个copy constructor时
3.当class声明了一个或多个virtual function时。
4.当class派生自一个继承串链,其中有一个或多个virtual base classes时。

转自:http://hi.baidu.com/dick_china/blog/item/8ffb0c35b42906d3a2cc2bee.html

 

A b = a; 编译器优化,一般不需要调用赋值操作,直接拷贝构造。

 

动态规划 LCS  F(m,n)+1

 

 

 

看到的都是假的,想到的都是错的!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值