C++ 子类父类的相互转换

我们都知道,实现子类到父类的转换比较简单,但如何实现父类到子类的转换呢?

现有一个场景如下,假设我有一个类,里边存有海量的数据作为成员变量,例如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:"<<a<<endl;
        cout<<"b:"<<b<<endl;
    }
    private:
    int a = 1;
    int b = 2;
};

class B:public A{
    public:
    B(int aa,int bb, int cc):A(aa,bb){
        c = cc;
    }

    void print(){
        cout<<"a:"<<geta()<<endl;
        cout<<"b:"<<getb()<<endl;
        cout<<"c:"<<c<<endl;
    }
    private:

    int c = 4;
};

例如我们创建一个子类B对象b,他的a、b和c的值分别是7 8 9:

B b(7,8,9);

我们可以直接使用任意类型转换,得到其父类的对象a:

A a = static_cast<A>(b);

(对象a和b不为同一地址)

但是对a采用同样的逻辑转为子类对象b2,则编译是错误的:

B b2 = static_cast<B>(a);

如果想要实现父类到子类的转换,可以使用指针。

    A *ap = &a;
    B *b2 = static_cast<B*>(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<A*>(b);
    B *b2 = static_cast<B*>(a);
    b2->print();

a:7

b:8

c:9

即想要真正实现父类到子类的转换,必需从子类到父类转换时就使用指针,即

B* -> A* ->B*

也就是说,父类到子类的转换必需以子类到父类的指针转换为前提,没有这个前提是不可能实现的。

 这样就是没有问题了。考虑这么几个问题,我们对以上变量中的b和a进行修改,是否会影响到b2指向的内容呢?

答案是肯定的,我们对取每个指针指向地址的地址:

    cout<<&(*b)<<endl;
    cout<<&(*a)<<endl;
    cout<<&(*b2)<<endl;

输出结果为:

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<A*>(bp);
    A a = *ap;
    B *bp2 = static_cast<B*>(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<A*>(bp);
    A a = *ap;
    A *ap2 =&a;
    B *bp2 = static_cast<B*>(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<B> bp = make_shared<B>(b);
    shared_ptr<A> ap = bp;
    auto bp2 = static_pointer_cast<B>(ap);
    B b2 = *bp2;

--------------------------------------

TIPS:

突然想到,如果发生内存交换操作,仍以上述类为例,假设两个父类指针aa1和aa2的内存发生了交换,其对应的子类b1和b2的前8个字节发生了交换,也是没问题的,但是后4个字节则不变,这就相当于保存的数据在两个对象中变了,所以涉及到此类内存交换问题,应该声明子类对象的大小!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值