C++:虚基类和虚继承、多重继承的问题、C++四种类型转换方式
一、虚基类和虚继承
1.定义
被虚继承的类就是虚基类。
类B虚继承类A,A是虚基类。A的实例化对象a占4个字节,虚继承之后,B的实例化对象b由8个字节变成12个字节,因为虚继承需要增加4个字节,并多了一个vbptr指针指向vbtable。
class A
{
public:
virtual void func() { cout << "call A::func()" << endl; }
private:
int ma;
};
class B :virtual public A
{
public:
void func() { cout << "call B::func()" << endl; }
private:
int mb;
};
2.虚继承会导致内存的改变

在0地址(相对地址)有一个 vbptr。该指针指向的是一个虚基类表 vbtable。
虚基类的数据一定要搬到我们派生类内存的最后面,然后在0地址(相对地址)有一个 vbptr。该指针指向的是一个虚基类表 vbtable!
虚基类表是干什么的呢?被虚继承后,虚基类的数据被放到最后面,再添加一个vbptr,指向vbtable。所以当有人来找 A::ma这个数据的时候还是会在第一个地址找,那么 vbptr 就应该告诉这个人 A::ma数据在内存中的位置。
3.虚基类的问题
这个问题仅存在于windows vs编译器,在Linux g++不存在该问题(释放内存时会自动完成偏移释放正确的内存地址)。
普通继承的派生类内存布局,先是基类再是派生类,那么基类指针指向派生类对象时,基类指针肯定指向的是派生类内存的起始部分;
如果是虚继承,那么基类指针指向派生类对象,永远指向的是派生类基类部分的起始地址。
int main()
{
//基类指针指向派生类对象,永远指向的是派生类基类部分的起始地址
A* p = new B();
p->func();
delete p;
return 0;
}
运行时,能显示call B::func(),但释放内存出错,因为释放内存从vbptr开始释放,但p所持有的是从虚基类部分开始的,因此delete操作从虚基类部分开始。

应该在栈上构建:
int main()
{
//基类指针指向派生类对象,永远指向的是派生类基类部分的起始地址
B b;
A* p = &b; // new B();
cout << "main p:" << p << endl;
p->func();
//delete p;
return 0;
}
二、菱形继承的问题
如下左图所示,B和C继承A,D继承B和C,就构成了菱形继承。A称作D的间接基类。
由此图可以看出,D的数据是有问题的,有间接基类A的多份数据ma。


综上两个图所示,c++多重继承是有问题的,那么怎么去避免呢?
我们按左图进行代码编写:
#include<iostream>
using namespace std;
class A
{
public:
A(int data) :ma(data) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
protected:
int ma;
};
/*=================================================================*/
class B :public A
{
public:
B(int data) :A(data), mb(data) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
protected:
int mb;
};
class C :public A
{
public:
C(int data) :A(data), mc(data) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
protected:
int mc;
};
/*==================================================================*/
class D :public B, public C
{
public:
D(int data) :B(data), C(data), md(data) { cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
protected:
int md;
};
int main()
{
D d(10);
return 0;
}
运行编译得到的结果:

由此可以看出,对A进行多份继承,这是有问题的!
解决方法:对B、C都采用虚继承A,使A的数据在D上。因此,D需要对A进行构造。
#include<iostream>
using namespace std;
class A
{
public:
A(int data) :ma(data) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
protected:
int ma;
};
/*=================================================================*/
class B :virtual public A
{
public:
B(int data) :A(data), mb(data) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
protected:
int mb;
};
class C :virtual public A //关键修改点1
{
public:
C(int data) :A(data), mc(data) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
protected:
int mc;
};
/*==================================================================*/
class D :public B, public C //关键修改点2
{
public:
//关键修改点3,需要对A进行构造
D(int data) :A(data), B(data), C(data), md(data) { cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
protected:
int md;
};
int main()
{
D d(10);
return 0;
}

总结:c++多重继承:
好处:可以做更多代码的复用;
坏处:派生类有多份间接基类的数据,这是设计的问题
→解决:对间接基类(A)进行继承的类(B、C)都采用虚继承
三、C++语言级别提供的四种类型转换方式
1.const_cast:去掉常量属性
使用const_cast必须保证转换类型与原数据常量类型相同!
const_cast<这里必须是指针或者引用类型>
int main()
{
const int a = 10;
int* p1 = (int*)&a;
int* p2 = const_cast<int*>(&a);
//double* p2 = const_cast<double*>(&a); 无法从“const int* ”转换为“double* ”
//int b = const_cast<int>(a); 无法从“const int”转换为“int”
return 0;
}
2.static_cast:提供编译器认为安全的类型转换
没有任何联系的类型之间的转换就会被否定,有联系就可以互相转换。
基类类型《==》派生类类型,可以用static_cast!
int main()
{
int a = 10;
char b = static_cast<int>(a); //char和int之间有联系,可以转换
//int* p = nullptr;
//short* b = static_cast<short*>(p); short*和short之间没有联系,不可以转换
return 0;
}
3.reinterpret_cast:类似c风格的强制类型转换
static_cast不支持转换的,可以使用reinterpret_cast进行强制转换,但没有安全性可言!
int main()
{
int* p = nullptr;
short* b = reinterpret_cast<short*>(p);
return 0;
}
4.dynamic_cast:主要用在继承结构中,可以支持RTTI类型识别的上下转换
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func() = 0;
};
class Derive1:public Base
{
public:
void func() { cout << "Derive1::func" << endl; }
};
class Derive2 :public Base
{
public:
void func() { cout << "Derive2::func" << endl; }
// Derive2实现新功能的API接口函数
void derive02func() { cout << "Derive2::derive02func" << endl; }
};
void showfunc(Base* p)
{
//dynamic_cast会检查p指针是否指向的是一个Derive2类型的对象?
//p->vfptr->vftable RTTI信息,如果是,dynamic_cast转换类型成功,
//返回给Derive2对象的地址,给pd2;如果不是,返回nullptr
Derive2* pd2 = dynamic_cast<Derive2*>(p);
if (pd2 != nullptr)
{
pd2->derive02func();
}
else
{
p->func();
}
}
int main()
{
Derive1 d1;
Derive2 d2;
showfunc(&d1);
showfunc(&d2);
return 0;
}
更多推荐

所有评论(0)