多态二:带有虚函数的虚拟继承

1、普通函数的继承

class Base
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
	int _b;
};
class Derived :public Base
{
public:
	int _d;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._b = 1;
	d._d = 2;
 	system("pause");
	return 0;
}

一、带有虚函数的虚拟继承:比普通的函数多了一个偏移量指针的地址

1、

class Base
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
	int _b;
};
class Derived :virtual public Base
{
public:
	int _d;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._b = 1;
	d._d = 2;
 	system("pause");
	return 0;
}class Derived :virtual public Base
{
public:
	int _d;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._b = 1;
	d._d = 2;
 	system("pause");
	return 0;
}

首先把基类中的成员继承下来,在加上派生类字节的大小,多出来的4个字节为一个指针,指向偏移量表格的地址。

2、把派生类中的函数改为虚函数,大小不发生改变:

class Base
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
	int _b;
};
class Derived :virtual public Base
{
public:
	virtual void Test2()
	{
		cout << "Derived::Test2()" << endl;
	}
	int _d;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._b = 1;
	d._d = 2;
 	system("pause");
	return 0;
}class Derived :virtual public Base
{
public:
	virtual void Test2()
	{
		cout << "Derived::Test2()" << endl;
	}
	int _d;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._b = 1;
	d._d = 2;
 	system("pause");
	return 0;
}

3、在派生类中新增加一个虚函数之后,大小增加四个字节,新增加的虚函数是单独拿出来存放的。

class Base
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
	int _b;
};
class Derived :virtual public Base
{
public:
	virtual void Test2()
	{
		cout << "Derived::Test2()" << endl;
	}
	virtual void Test3()
	{
		cout << "Derived::Test3()" << endl;
	}
	int _d;
};
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	d._b = 1;
	d._d = 2;
 	system("pause");
	return 0;
}

3、采用如下打印方式,打印的都是派生类的内容

class Base
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
	int _b;
};
class Derived :virtual public Base
{
public:
	virtual void Test2()
	{
		cout << "Derived::Test2()" << endl;
	}
	virtual void Test3()
	{
		cout << "Derived::Test3()" << endl;
	}
	int _d;
};
typedef void(*PVTF)();
void PrintVTP(Base &b, const string &str)
{
	PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
	cout << str << endl;//引用之前先打印str
	while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
	{
		(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
		++pVTF;
	}
	cout << endl;
}
void PrintVTP(Derived &b, const string &str)
{
	PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
	cout << str << endl;//引用之前先打印str
	while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
	{
		(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
		++pVTF;
	}
	cout << endl;
}
int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;

 	d._b = 1;
	d._d = 2;
	PrintVTP(d,"Derived VFT---->Base");
	PrintVTP(d, "Derived VFT---->Derived");
	system("pause");
	return 0;
}

因为两个函数同时给过来之后会形成重载

改进后:

int main()
{
	cout << sizeof(Derived) << endl;
	Derived d;
	Base &b = d;
	d._b = 1;
	d._d = 2;
	PrintVTP(b, "Derived VFT---->Base");
	PrintVTP(d, "Derived VFT---->Derived");
	system("pause");
	return 0;
}

即可顺利打印。

4、带有虚函数的菱形虚拟继承

class Base
{
public:
	virtual void Test1()
	{
		cout << "B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "B::Test2()" << endl;
	}
	int _b;
};
class C1 :virtual public Base
{
public:
	virtual void Test1()
	{
		cout << "C1::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "C1::Test3()" << endl;
	}
	int _c1;
};
class C2 :virtual public Base
{
public:
	virtual void Test2()
	{
		cout << "C2::Test2()" << endl;
	}
	virtual void Test4()
	{
		cout << "C2::Test4()" << endl;
	}
	int _c2;
};
class D :public C1, public C2
{
public:
	virtual void Test1()
	{
		cout << "D::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "D::Test3()" << endl;
	}
	virtual void Test4()
	{
		cout << "D::Test4()" << endl;
	}
	virtual void Test5()
	{
		cout << "D::Test5()" << endl;
	}
 	int _d;
};
typedef void(*PVTF)();
void PrintVTP(C1 &b, const string &str)
{
	PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
	cout << str << endl;//引用之前先打印str
	while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
	{
		(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
		++pVTF;
	}
	cout << endl;
}
void PrintVTP(C2 &b, const string &str)
{
	PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
	cout << str << endl;//引用之前先打印str
	while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
	{
		(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
		++pVTF;
	}
	cout << endl;
}
typedef void(*PVTF)();
void PrintVTP(Base &b, const string &str)
{
	PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
	cout << str << endl;//引用之前先打印str
	while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
	{
		(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
		++pVTF;
	}
	cout << endl;
}
int main()
{
	cout << sizeof(D) << endl;
	D d;
	d._b = 1;
	d._c1 = 2;
	d._c2 = 3;
	d._d = 4;
	Base &b = d;//通过d打印D中的虚表
	PrintVTP(b, "D VFT--->B");
	C1& c1 = d;//通过c1打印c1中的虚表
	PrintVTP(c1, "D VFT--->C1");
	C2& c2 = d;
	PrintVTP(c2, "D VFT--->C2");

	system("pause");
	return 0;
}

派生类把自己新增加的虚函数加到一个虚表的后面。

(2)菱形虚拟继承的方式,当在C1或C2中分别加一个构造(析构)函数时,多出来了4个字节,在C1和C2中同时加上构造(析构)函数也是多出了4个字节。但是在基类中增加一个构造(析构)函数时,没有增加四个字节;在派生类增加一个构造(析构)函数时也会增加4个字节。

class C1 :virtual public Base
{
public:
	C1()
	{}
	virtual void Test1()
	{
		cout << "C1::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "C1::Test3()" << endl;
	}
	int _c1;
};C1()
	{}
	virtual void Test1()
	{
		cout << "C1::Test1()" << endl;
	}
	virtual void Test3()
	{
		cout << "C1::Test3()" << endl;
	}
	int _c1;
};

相当于在派生类和基类之间多插入了4个字节的0。

多态的缺陷:a、对象里面多出来了四个字节;

                    b、程序运行效率降低了(需要在虚表中找虚函数的地址)

得意那么,我们认识了这么久的虚函数,你知道哪些函数不能作为虚函数么?我们一起好好回顾一下吧。

(1)全局作用域里面的函数不能作为虚函数(普通函数不能作为虚函数):因为虚拟关键字只能出现在虚拟继承(一种继承方式);虚拟继承只能出现在成员函数的位置,把普通函数给成虚函数不能实现多态,一般情况下形成多态是根据基类类型的指针或者引用来调用的(必须时是成员函数,普通函数和类没有任何关系,不能作为虚函数)

(2)友元函数不能作为虚函数(友元函数不是类的成员函数):

(3)静态成员函数不能作为虚函数

虚函数是通过基类类型的指针或者引用来调用的,而静态成员的调用方式可以不通过对象来调用。虚函数一定要通过对象来调用,会产生矛盾。

(4)类的一些其他成原函数:

a、构造函数不能作为虚函数:(a1)调用虚函数通过基类对象的指针或者引用,构造函数调用结束之后才会产生对象,没有执行构造函数就没有产生对象。没有对象就不能调用虚的构造函数。(a2)虚函数的调用必须通过虚表,找到虚函数,构造函数没有执行,对象不完整,带有虚函数的类要在构造函数里面把虚表的地址放到对象的前四个字节中去,但是此时构造函数没有调用,则不能产生对象,会发生矛盾。(构造函数没有执行,对象不完整)

b、拷贝构造函数不能作为虚函数,拷贝构造函数也是构造函数

c、析构函数可以作为虚函数:

class Base
{
public:
	Base()
	{
		cout << "Base::Base()" << endl;
	}
	virtual ~Base()
	{
		cout <<"Base()::~Base()" << endl;
	}
};

一般建议将析构函数给成虚函数,因为在继承体系下,有的情况下没有把基类的析构函数给成虚构函数会产生内存泄漏:

该代码中构造派生类的对象,但是最后调用了基类中的析构函数而没有调用派生类中的析构函数

class Base
{
public:
	Base()
	{
		cout << "Base::Base()" << endl;
	}
	~Base()
	{
		cout <<"Base()::~Base()" << endl;
	}
};
class Derived :public Base
{
public:
	Derived()
		:_p(new int[10])
	{
		cout << "Derived::Derived()" << endl;
	}
	~Derived()
	{
		cout << "Derived::~Derived()" << endl;
		if (_p)
			delete[] _p;
	}
	int *_p;
};
int main()
{
	Base *d = new Derived;//基类的指针可以指向派生类的对象
	delete d;
	system("pause");
 	return 0;
}

但是把基类的析构函数给成虚函数之后即可正确释放

class Base
{
public:
	Base()
	{
		cout << "Base::Base()" << endl;
	}
	virtual ~Base()
	{
		cout <<"Base()::~Base()" << endl;
	}
};

把派生类中的析构函数给成虚函数,在继承体系里面,会构成重写。释放d所指向的对象,需要调用析构函数,在继承体系里面,该析构函数是一个虚函数,派生类前四个字节中放的地址,是虚函数的地址,那么析构函数则是派生类的析构函数。。如果没有给成析构函数,则多态的条件不成熟,基类和派生类不能构成重写,虚构函数不能放到虚表中去,此时会把d当作Base*类型的指针,所以此时销毁对象只会把对象当作基类的对象释放,调用基类里面的析构函数。所以在继承体系里面最好把基类中的析构函数给成虚函数。只要派生类里面管理资源,基类里面的析构函数一定要给成虚函数,防止出现内存泄漏。

d、赋值运算符的重载:不能实现多态调用,因为参数列表不一样。

class Base
{
public:
	Base()
	{
		cout << "Base::Base()" << endl;
	}
	virtual ~Base()
	{
		cout <<"Base()::~Base()" << endl;
	}
	virtual Base& operator=(const Base& b)
	{
		return *this;
	}
};
class Derived :public Base
{
public:
	Derived()
		:_p(new int[10])
	{
		cout << "Derived::Derived()" << endl;
	}
	virtual Derived& operator=(const Derived& d)
	{
		return *this;
	}
	~Derived()
	{
		cout << "Derived::~Derived()" << endl;
		if (_p)
			delete[] _p;
	}
	int *_p;
};

可以采用协变的方式来构成重载

virtual Derived& operator=(const Base& d)
	{
		return *this;
	}

但是此时只能把派生类的对象当做基类类型的对象来使用,相当于基类类型的对象给派生类类型的对象赋值,会产生问题。(基类对象比较小,派生类对象比较大,用基类对象给派生类对象赋值,只能赋值一部分,赋值不完整;而且此时不是同类对象给同类对象的赋值)此时只能通过基类对象赋值,可能发生混乱。虽然赋值运算符的重载可以给成虚函数,建议不要把赋值运算符的重载给成虚函数采用这种方法,参数列表必须给为基类对象的引用,若是给成派生类对象的引用,不能构成重写,不能给位派生类对象的引用。给成基类对象的引用,相当于基类部分给基类部分赋值。采用这种方式,构成多态,无论采用基类对象还是派生类对象赋值方式都是用基类赋值。

猜你喜欢

转载自blog.csdn.net/xuruhua/article/details/80945377
今日推荐