c++オブジェクトカット(Object Slicing)
派生クラスオブジェクトをベースクラスオブジェクトに割り当てると、オブジェクトカットが発生します.(ベースクラスオブジェクトで派生クラスオブジェクトを強制的に変換する場合もあります)
オブジェクトカットは何が起こりますか?
shp=rect;CShapeのデフォルト付与関数が呼び出され、shpのCShape属性値はrectと同じですが、その虚関数テーブルポインタはベースクラスCShape虚関数テーブルを指します.
((CShape)rect).draw();CShapeのデフォルトのコピーコンストラクタが呼び出され、ベースクラスCShapeの虚関数テーブルを指す虚関数テーブルを指す中間変数が生成されます.
マルチステートの実装はポインタと参照によって行われる.オブジェクトの変換は、オブジェクトの切断のみをもたらし、マルチステートを実現することはできません.
次の2つの文の違いに注意してください.
*pShape=rect;//オブジェクトカットpShape=▭//マルチステート
ベースクラスと派生クラスオブジェクト間の値付けの問題:
class A { } Class B:public A { } A a_object; B b_object;
(1)a_boject=b_object;
(2)b_object=a_boject;
以上の2つの説明について説明します.
(1)a_boject=b_object;default A::operator=、コンパイラによって自動的に生成され、その関数宣言はA operator=(A rhs)にほぼ類似しており、どうせ=番号の右側にはAのオブジェクトが必要であり、bobjectはAのサブクラスオブジェクトとしても実行可能であるが、伝達中に「切断」が発生する.
(2)b_object=a_boject;default B::operator=を呼び出し、コンパイラによって自動的に生成されます.その関数宣言は、B operator=(B rhs)にほぼ似ています.ここで=番号の右側にはBのオブジェクトが要求されています.aobjectはこの場合は実行できません.(コンパイラはエラーを報告します)
この場合、単にB::operator=をリロードしても始まらない.operator=関数パラメータがBオブジェクトでなければならないという事実を変えることはできないからだ.b_を実現するにはobject=a_boject;強制型変換関数を再ロードしたり、コンパイラの暗黙型変換機能を使用したりできます.たとえば、次のようになります.
小結:Classオブジェクトをパラメータとして使用する場合、pass by valueをreference to constで置き換えると、オブジェクトの切断が回避されます.
ベースクラスポインタと派生クラスポインタの相互変換
1.ベースクラスオブジェクト2を直接ベースクラスポインタで参照し、派生クラスオブジェクト3を直接派生クラスポインタで参照し、ベースクラスポインタで派生クラスオブジェクトを参照します.派生クラスオブジェクトもベースクラスのオブジェクトであるため、この参照は安全ですが、ベースクラスメンバーのみを参照できます.派生クラスにのみ存在するメンバーをベースクラスポインタで参照しようとすると、コンパイラは構文エラーを報告します.(この問題を解決する答えは虚関数と多態性です)4ベースクラスのオブジェクトを派生クラスポインタで参照します.この参照方式は構文エラーを引き起こします.派生クラスポインタは、まずベースクラスポインタに強制的に変換する必要があります.この方法は安全です.
侯捷の深入浅出MFCの第2章C++の重要な性質の中で:1、もしあなたが1つの“ベースクラスのポインタ”で1つの“派生クラスのオブジェクト”を指すならば、そのポインタを通じてあなたはこのベースクラスが定義した関数2を呼び出すしかなくて、もしあなたが1つの“派生クラスのポインタ”で1つの“ベースクラスのオブジェクト”を指すならば、あなたはまず明らかな転換操作(explicit cast)をしなければなりませんこのやり方は危険だ.3、ベースクラスと派生クラスの両方に「同じ名前の関数」が定義されている場合、オブジェクトポインタによってメンバー関数が呼び出された場合、その関数が呼び出されたかどうかは、ポインタが実際に指しているオブジェクトのタイプではなく、ポインタの元のタイプによって決まる必要があります.これは第1の点と意味が通じます.
ベースクラスは最小の決定情報を表し、派生クラスはより多くの決定情報を表す.
ベースクラスのポインタは、派生クラスのオブジェクトを指すことができ、ポインタが自分の所望の情報量よりも多くの情報を収容するオブジェクトを指すことができることを意味する.このような操作が発生すると、このポインタは、定義されたときに期待されていなかった、あるいは予想されていなかった追加の操作を行うことができる.
これに対して、派生クラスのポインタはベースクラスのオブジェクトを指すことはできません.このようにすれば、この派生クラスのポインタは定義されたときに所望されるすべてのことの一部ができなくなるからです.なぜなら、そのベースクラスを指すオブジェクトには、派生クラスが派生したときに新たに増加する方法が提供されていないからです.しかし、C++は、ポインタ定義の後、少なくとも期待されることと同じように多くのことをすることができることを保証します.したがって,派生クラスポインタがベースクラスオブジェクトを指す意図は当然許されない.
興味深いコードを添付します.
オブジェクトカットは何が起こりますか?
#include "stdafx.h"
#include <iostream>
using namespace std;
class CShape
{
public:
CShape ()
{
m_color=0;
}
~CShape(){}
virtual void draw()
{
cout<<"This is a shape!"<<endl;
cout<<m_color<<endl;
}
double m_color;
};
class CRect: public CShape
{
public:
CRect()
{
m_width=5;
m_height=4;
m_color=1;
}
~CRect(){};
double size()
{
return m_width*m_height;
}
virtual void draw()
{
cout<<"This is a rect!"<<endl;
cout<<m_color<<endl;
}
double m_width;
double m_height;
};
int main(int argc, char* argv[])
{
CShape shp;
CRect rect;
shp = rect;
shp.draw();//
((CShape)rect).draw();// ,
CShape *pShape=new CShape();
*pShape=rect;//
pShape->draw();
pShape=▭// ,
pShape->draw();
((CShape*)(&rect))->draw(); // ,
system("pause");
return 0;
}
出力結果:This is a shape!
1
This is a shape!
1
This is a shape!
1
This is a rect!
1
This is a rect!
1
……
shp=rect;CShapeのデフォルト付与関数が呼び出され、shpのCShape属性値はrectと同じですが、その虚関数テーブルポインタはベースクラスCShape虚関数テーブルを指します.
((CShape)rect).draw();CShapeのデフォルトのコピーコンストラクタが呼び出され、ベースクラスCShapeの虚関数テーブルを指す虚関数テーブルを指す中間変数が生成されます.
マルチステートの実装はポインタと参照によって行われる.オブジェクトの変換は、オブジェクトの切断のみをもたらし、マルチステートを実現することはできません.
次の2つの文の違いに注意してください.
*pShape=rect;//オブジェクトカットpShape=▭//マルチステート
ベースクラスと派生クラスオブジェクト間の値付けの問題:
class A { } Class B:public A { } A a_object; B b_object;
(1)a_boject=b_object;
(2)b_object=a_boject;
以上の2つの説明について説明します.
(1)a_boject=b_object;default A::operator=、コンパイラによって自動的に生成され、その関数宣言はA operator=(A rhs)にほぼ類似しており、どうせ=番号の右側にはAのオブジェクトが必要であり、bobjectはAのサブクラスオブジェクトとしても実行可能であるが、伝達中に「切断」が発生する.
(2)b_object=a_boject;default B::operator=を呼び出し、コンパイラによって自動的に生成されます.その関数宣言は、B operator=(B rhs)にほぼ似ています.ここで=番号の右側にはBのオブジェクトが要求されています.aobjectはこの場合は実行できません.(コンパイラはエラーを報告します)
この場合、単にB::operator=をリロードしても始まらない.operator=関数パラメータがBオブジェクトでなければならないという事実を変えることはできないからだ.b_を実現するにはobject=a_boject;強制型変換関数を再ロードしたり、コンパイラの暗黙型変換機能を使用したりできます.たとえば、次のようになります.
class A
{
public:
A(){}
};
class B:public A
{
public:
B(){}
B(A a){}//
};
A aobject;
B bobject;
int main()
{
a_object=b_object;
b_object=a_object;
return 0;
}
小結:Classオブジェクトをパラメータとして使用する場合、pass by valueをreference to constで置き換えると、オブジェクトの切断が回避されます.
ベースクラスポインタと派生クラスポインタの相互変換
1.ベースクラスオブジェクト2を直接ベースクラスポインタで参照し、派生クラスオブジェクト3を直接派生クラスポインタで参照し、ベースクラスポインタで派生クラスオブジェクトを参照します.派生クラスオブジェクトもベースクラスのオブジェクトであるため、この参照は安全ですが、ベースクラスメンバーのみを参照できます.派生クラスにのみ存在するメンバーをベースクラスポインタで参照しようとすると、コンパイラは構文エラーを報告します.(この問題を解決する答えは虚関数と多態性です)4ベースクラスのオブジェクトを派生クラスポインタで参照します.この参照方式は構文エラーを引き起こします.派生クラスポインタは、まずベースクラスポインタに強制的に変換する必要があります.この方法は安全です.
侯捷の深入浅出MFCの第2章C++の重要な性質の中で:1、もしあなたが1つの“ベースクラスのポインタ”で1つの“派生クラスのオブジェクト”を指すならば、そのポインタを通じてあなたはこのベースクラスが定義した関数2を呼び出すしかなくて、もしあなたが1つの“派生クラスのポインタ”で1つの“ベースクラスのオブジェクト”を指すならば、あなたはまず明らかな転換操作(explicit cast)をしなければなりませんこのやり方は危険だ.3、ベースクラスと派生クラスの両方に「同じ名前の関数」が定義されている場合、オブジェクトポインタによってメンバー関数が呼び出された場合、その関数が呼び出されたかどうかは、ポインタが実際に指しているオブジェクトのタイプではなく、ポインタの元のタイプによって決まる必要があります.これは第1の点と意味が通じます.
#include <iostream>
#include <stdlib.h>
using namespace std;
class A
{
public:
char str[20];
void f(){cout<<"class A"<<endl;}
void fff(){cout<<"class A's str "<<str<<endl;}
void add(){cout<<"class A address: "<<this<<endl;}
};
class B:public A
{
public:
int i;
char sb[20];
B(){cout<<"class B constructor is run."<<endl;}
~B(){cout<<"class B destructor is run."<<endl;}
void f(){cout<<"class B"<<endl;}
void ff(){cout<<"class B "<<i<<str<<sb<<endl;}
void add(){cout<<"class B address: "<<this<<endl;}
};
int main(int argc, char *argv[])
{
//
A b;
// ,
A *pa=&b;
pa->add();
// , , pa B A .
//pa B , , , pa A ,
// . ,
B *pb=(B *)pa;
// , . .
// . . B ,
// 。 pb->add(); add pb
// this
pb->add();
// pa , ,
// , .
pb->i=100;
char dsd[100];
strcpy(pb->sb, " class B's sb.");
strcpy(pb->str, " class A's str.");
//pb->f() , ,
// . ,
pb->f();
pb->ff();
pb->fff();
system("PAUSE");
return 0;
}
ベースクラスは最小の決定情報を表し、派生クラスはより多くの決定情報を表す.
ベースクラスのポインタは、派生クラスのオブジェクトを指すことができ、ポインタが自分の所望の情報量よりも多くの情報を収容するオブジェクトを指すことができることを意味する.このような操作が発生すると、このポインタは、定義されたときに期待されていなかった、あるいは予想されていなかった追加の操作を行うことができる.
これに対して、派生クラスのポインタはベースクラスのオブジェクトを指すことはできません.このようにすれば、この派生クラスのポインタは定義されたときに所望されるすべてのことの一部ができなくなるからです.なぜなら、そのベースクラスを指すオブジェクトには、派生クラスが派生したときに新たに増加する方法が提供されていないからです.しかし、C++は、ポインタ定義の後、少なくとも期待されることと同じように多くのことをすることができることを保証します.したがって,派生クラスポインタがベースクラスオブジェクトを指す意図は当然許されない.
興味深いコードを添付します.
// virtual Function.cpp : 。
#include "stdafx.h"
#include <iostream>
using namespace std;
class IHello
{
public:
virtual void hello1() = 0;
virtual void hello2() = 0;
};
class IWorld
{
public:
virtual void world1() = 0;
virtual void world2() = 0;
};
class HelloWorld: public IHello,public IWorld
{
public:
void hello1();
void hello2();
void world1();
void world2();
};
void HelloWorld::hello1()
{
cout<<"hello1!"<<endl;
}
void HelloWorld::hello2()
{
cout<<"hello2!"<<endl;
}
void HelloWorld::world1()
{
cout<<"world1!"<<endl;
}
void HelloWorld::world2()
{
cout<<"world2!"<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
IHello* pIHello = new HelloWorld;
IWorld* pIWorld = (IWorld*)(void*)pIHello;
cout<<"********************First test********************"<<endl;
pIWorld->world1();
pIWorld->world2();
cout<<"********************Second test********************"<<endl;
pIWorld = new HelloWorld;
pIWorld->world1();
pIWorld->world2();
system("pause");
return 0;
}