我们都知道,实现子类到父类的转换比较简单,但如何实现父类到子类的转换呢?
现有一个场景如下,假设我有一个类,里边存有海量的数据作为成员变量,例如cv::Mat等等,占用内存大小较大,可能以M或G计,我们想要对其的若干个对象操作,例如排序等等,该操作仅会使用他的部分成员变量,比如时间戳(double)、类型(字符串)、大小(size_t)等等,并且这些成员变量占内存较小,如果直接使用该类进行操作,那么频繁地交换、储存临时变量将占内存比较大,其中一个思路是将其改造为子类,其父类仅有需要进行操作的成员变量,在进行操作前我们将对象由子类转为父类,然后从父类操作后的结果复原出子类对象。
例如上图,将对象由类CLASS转为FATHER时,SON存在但是FATHER没有的成员变量或者函数将会丢失,那么怎么从FATHER再复原回这些呢?直接的类的强转得到的子类仅有的成员变量将会是不对的:
本文的类将以以下两个类为基础,B为A的子类,a和b为父类A的变量,并且B继承于A,B.c为B相比于A多出的成员变量。
class A{
public:
A(int aa,int bb){
a = aa;
b = bb;
}
int geta(){
return a;
}
int getb(){
return b;
}
void print(){
cout<<"a:"<
例如我们创建一个子类B对象b,他的a、b和c的值分别是7 8 9:
B b(7,8,9);
我们可以直接使用任意类型转换,得到其父类的对象a:
A a = static_cast(b);
(对象a和b不为同一地址)
但是对a采用同样的逻辑转为子类对象b2,则编译是错误的:
B b2 = static_cast(a);
如果想要实现父类到子类的转换,可以使用指针。
A *ap = &a;
B *b2 = static_cast(ap);
b2->print();
即创建一个父类指针ap指向刚刚强转的a,然后再强转该指针为B *类型的指针b2。
输出为:
a:7
b:8
c:4197229
虽然正确调用了子类的print函数,但是c的值却是错误的。因为a根本没有c啊!由于ap和b2指向大小不同,(分别是8和12),并且ap指向的地址和最开始的子类对象b并不是同一个地址。所以在强转时认为ap指向区域之后的4字节为c,在这里为4197229,而不是9。因此虽然实现了父类到子类的转换,但是子类存在父类不存在的值不是咱们想要的。因此从子类到父类的转换时,就要使用指针进行转换,才能保证一直是同一个值:
B *b =new B(7,8,9);
A *a = static_cast(b);
B *b2 = static_cast(a);
b2->print();
a:7
b:8
c:9
即想要真正实现父类到子类的转换,必需从子类到父类转换时就使用指针,即
B* -> A* ->B*
也就是说,父类到子类的转换必需以子类到父类的指针转换为前提,没有这个前提是不可能实现的。
这样就是没有问题了。考虑这么几个问题,我们对以上变量中的b和a进行修改,是否会影响到b2指向的内容呢?
答案是肯定的,我们对取每个指针指向地址的地址:
cout<<&(*b)<
输出结果为:
0x701c20
0x701c20
0x701c20
即三个指针都是指向了统一块内存,在这里为12字节,但是指针a仅会使用前8个字节。
任意一个阶段对对象内容的修改都会影响到最后b2指向的内容。
但是如果我们对指针a进行如下操作:
A aa = *(a);
则相当于从a指向的内存拷贝了一份,起名叫aa,如果我们还是对a进行强转类型的话,aa操作的区域和其他指针操作的区域并不是同一块内存,所以他们之间的操作是互不影响的。
B b(7,7,7);
B*bp=&b;
A *ap = static_cast(bp);
A a = *ap;
B *bp2 = static_cast(ap);
bp2->print();
a.change(6,6);
a.print();
bp2->print();
bp2->change(8);
a.print();
输出为
a:7
b:7
c:7
a:6
b:6
a:7
b:7
c:7
a:6
b:6
但是,如果子类指针是从aa的指针强转,那么后续不管是子类指针b2的改动(b2->change)还是aa的改动(aa.change),都会互相影响:
B b(7,7,7);
B*bp=&b;
A *ap = static_cast(bp);
A a = *ap;
A *ap2 =&a;
B *bp2 = static_cast(ap2);
bp2->print();
a.change(6,6);
B b2 = *bp2;
b2.print();
输出为:
a:7
b:7
c:65535
a:6
b:6
c:65535
可知,由于sizeof(*ap)的结果为8,所以创建对象a的时候只拷贝了8个字节,因此变量c是缺失的,最后发生了被迫访问其他区域的行为。因此若要对子类进行操作,那么操作过程要一直以指针形式进行。
当然,最后用智能指针替换普通指针也是类似道理:
B b(7,7,7);
shared_ptr bp = make_shared(b);
shared_ptr ap = bp;
auto bp2 = static_pointer_cast(ap);
B b2 = *bp2;
--------------------------------------
TIPS:
突然想到,如果发生内存交换操作,仍以上述类为例,假设两个父类指针aa1和aa2的内存发生了交换,其对应的子类b1和b2的前8个字节发生了交换,也是没问题的,但是后4个字节则不变,这就相当于保存的数据在两个对象中变了,所以涉及到此类内存交换问题,应该声明子类对象的大小!