Object Slice(对象切割),很令人头晕的东西!

来源:互联网 发布:电子地图 数据采集 编辑:程序博客网 时间:2024/04/25 03:41

 #include <iostream.h>
class A
{
public:virtual void print(){cout<<"A::print()"<<endl;}
};

class B:public A
{
public:virtual void print(){cout<<"B::print()"<<endl;}
};

class C:public A
{
public:virtual void print(){ cout<<"C::print()"<<endl;}
};

void printfun(A a)
{
a.print();
}
void main()
{
A a;
B b;
C c;

printfun(a);
printfun(b);
printfun(c);
}
将派生类operator=给基类的一个引用,对象会被切割!导致派生类的信息会被切掉!

输出结果:
A::print()
A::print()
A::print()

因为printfun的参数是A,而不是A&,所以这里是object slice而不是多态起作用。

如果是void printfun(A& a)
{
a.print();
}
就会输出
A::print()
B::print()
C::print()
那就用到虚函数了,如果父类是虚函数,那么子类有的就调用子类,子类没有的就调用父类.

来看另一道题目:

#include   <iostream>  
  using   namespace   std;  
   
  class   Base  
  {  
  public:  
  Base()  
  {  
  cout   <<   "Base   ctor()   "   <<   endl;  
  }  
  virtual void   print()  
  {  
  cout   <<   "Base::print()"   <<   endl;  
  }  
  };  
   
  class   Derived:public   Base  
  {  
  public:  
  Derived()  
  {  
          cout   <<   "Derived   ctor()"   <<   endl;  
  }  
  void   print()  
  {  
  cout   <<   "Derived::print()"   <<   endl;  
  }  
   
   
  };  
   
  int   main()  
  {  
  Base         _base;  
  Derived   _derived;   
  Base*   pBase   =   new Derived();  
  pBase->Base::print();  
  pBase->print();  

return   0;
  }

输出结果:

Base   ctor()
Base   ctor()
Derived   ctor()
Base   ctor()
Derived   ctor()
Base::print()
Derived::print()
Press any key to continue
若改成如下代码:

Base&   pBase   =   _derived;  
  pBase.Base::print();  
  pBase.print();  
  //pBase.Derived::print(); 报错 

输出结果:

Base   ctor()
Base   ctor()
Derived   ctor()
Base::print()
Derived::print()
Press any key to continue
由此可见,

Base*   pBase   =   new Derived();  等价于Base& pBase = _derived;也等价于 Base* pBase = &_derived;

在看一道例题:

#include   <iostream.h>  
   
  class   A  
  {  
  public:  
   void   Mark()   {cout<<"A::Mark"<<endl;}  
  };  
   
  class   B   :   public   A  
  {  
  public:  
  void   Mark()    {   cout<<"B::Mark"<<endl;   }  
  };  
   
  int   main()  
  {  
  B   b;  
  b.A::Mark();  
  b.B::Mark();  
  B*   pB   =   &b;  
  pB->A::Mark();                   
  pB->B::Mark();                 
  A*   pA   =   pB;                 
  pA->A::Mark();  
  //pA->B::Mark();  
   
  return   0;  
  }  

输出结果:

A::Mark
B::Mark
A::Mark
B::Mark
A::Mark
Press any key to continue
但通過派生類對象的指針或引用來調用虛函數﹐則不會發生object   slice﹐但是若直接將派生類對象傳給基類型參數則會發生此事。  

pA是指向A的指针,他不能操作其他类的成员(包括它的派生类成员)    不管是用指针,还是引用,只要是对成员的访问都要在编译期确定。    pA->B::Mark();   //出错的原因是找不到   A::B::Mark这个成员   即使Mark是虚函数,也不能这样使用  B::Mark始终不是A的成员   虚函数只是用于保留接口名。   
    如果Mark是虚函数   pA->Mark();   在编译期看上去是调用的A::Mark,但是在运行期确是调用的B::Mark

可以看一下,下面的程序:

#include   <iostream>  
  using   namespace   std;  
  class   Base  
  {  
  public:  
  Base()  
  {  
  }  
   
  };  
   
  class   Derived:public   Base  
  {  
  public:  
  Derived()  
  {  
  }  
  virtual   void   print()  
  {  
  cout   <<   "Derived::print()   called"   <<   endl;  
  }  
  };  
   
  int   main()  
  {  
  Base*   pBase   =   new   Derived();  
  pBase->print();  
  return   0;  
   
  }
结果如下:

error C2039: 'print' : is not a member of 'Base'

首先来看看代码  
  1、有Base::print()这个成员吗?  
  很明显,所以不能用Base*指针去访问print()。解决方法是,如果你不想给Base提供print()方法,那么就可以给Base定义一个纯虚函数,用以保留一个接口。但是在这里,你的Base看上去或在用途上不能保证是一个抽象的基类,所以最好定义成虚函数,而不是纯虚函数。  
  #include   <iostream>  
  using   namespace   std;  
  class   Base{  
  public:  
  Base(){}  
                    virtual   void   print(){}   //这里  
  };  
   
  class   Derived:public   Base{  
  public:  
  Derived(){}  
  virtual   void   print(){  
  cout   <<   "Derived::print()   called"   <<   endl;  
  }  
  };  
  //调用  
  Base*   pBase   =   new   Derived();  
  pBase->print();   编译器找到Base::print()这个接口,所以正确。而对于到底是调用哪个print()会在运行期由vptr决定。  
Q2:vptr和vtbl都是在compile期生成的,并且在执行期不能被改变,vptr的初始化一般发生在对象的ctor中;  
  Q3:必须提出一点,一般性的继承和虚拟继承在实现上有着较大的差别,所以必须分开讨论。  
          1.首先我们来看,一般性的继承。此时,我们可以说,每个对象只能拥有一对vtbl和vptr,所以,是不能单纯的使用copy得到的,两者将在对象的ctor中设置,并且对virtual   function在vtbl中的索引位置以及值都会根据子类对父类中重新定义virtual   function的情况加以调整,但有一点必须注意的是destructor,每个类的vtbl中,如果存在这一项,它都将指向被该类定义的destructor,而非父类的;如果没有定义,对不起,可能出错(视不同的编译器而定)

2.再来看虚拟继承和多继承的情况,嗯,这可是个“大”问题,呵呵,在cpp中,有很多的“凡是”规则可以遵循,比如凡是多态的实现必须借助于“指针”或“索引”完成等等,但其中很多一遇到这个“virtual   base   class”,尤其是带有“菱形”的继承结构,就玩完!:(在这样的继承系统之中,对象是可能拥有一个以上的vtbl的,呵呵应该是vtbls和vptrs了,一般是每个父类对应一个vtbl和一个vptr。说了怎么多废话,但关键的在下面:由于无论时多继承也好,虚拟继承也好,它们必须支持“多态”这个关键性质,这就意味着,它们必须能完成下面的任务:凭借指向父类的指针、引用能完成对子类中成员函数的调用。所有,此时,父类的虚表必须根据其它父类和子类的情况进行“调整”,这种调整将在子类的ctor中完成(可能会进一步借助与父类的ctor),也就是说拥有相同父类的子类对象其所含的父类对应的vtbl的内容将可能是不同的,必须在其各自的ctor中加以调整。

对于单一的继承并且是非virtual继承的类而言,其只能拥有一个vtbl,我想这是来自base的,但注意并不是原来的那个,而是经过调整过得,比如Derived   class对base的声明或定义的virtual   function进行了重载等,至少对应于destructor的那一项已经变成了Derived的了。所以,对应特定的类,这个表也只要有一个就可以了,因而不存在对象之间vtbl的copy问题,而vptr可以认为是类的一个“指针”成员,不过其在对象之间的拷贝由编译器插入一定的代码加以完成

“对象切割”是一个基于“内存”操作上的概念,楼主最初的程序之所以有问题,并非由于内存的变化引起的,引起变化的只是对某块内存的“解释”上;因而很难和切割有什么瓜葛。  
  当Base&   pBase   =   _derived;被写下时,我们必须清楚从compiler的角度上是如果看待pBase的,它是类型Base的一个引用,使用它来标记_derived所对应的那块“内存”,我们将只能看到_derived中属于Base的那部分,由于vtpr一般位于Base所在的那块内存中,并且已经经过compiler的调整(根据Derived中对virtual   function的重载情况),所以“多态”可以发生,但对应Base之外的那些内容,在compiler来说,对不起,单使用pBase是看不到的。


原创粉丝点击