C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(1)

        扩展类为许多问题打开了潘多拉魔盒。类的什么特点可以或不可以修改?什么是非公共继承?什么是虚的基类?这些问题,可能更多,会在接下来进行讨论。

1、修改重载的成员函数的返回类型

        在很大程度上,重载成员函数的原因是修改它的实现。而有时候,可能会想修改成员函数的其它特点,如它的返回类型。

        经验法则是用准确的成员函数声明来重载成员函数,或者是基类使用的成员函数原型。这种实现可以修改,但是原型保持不变。

        事实可以不是这样,在c++中,只要基类的成员函数的返回类型是对类的指针或者引用,在继承类中的返回类型是对后代的,也就是说,更明确的类,的指针或者引用,重载成员函数就可以修改返回类型。这样的类型叫做共变返回类型。这个属性有时候会很方便,在基类和继承类以平等层次结构共同工作时,也就是说,另外一组的类是稍微粘边的,但是相关的,与第一个类层次结构。

        例如,考虑一个基本的汽车模拟器,可能会有两个类层次结构来模型化不同的真实世界的对象,但是很明显是相关的。第一个就是Car层次结构。基类,Car,有继承类GasolineCar与ElectricalCar。同样的,还有另外一个类的层次结构,其基类可能我的天PowerSource,继承类为GasolinePowerSource与ElectricalPowerSource。下面展示了两种类层次结构。

        让我们假设动力源可以打印自身的类型,汽油动力源有一个成员函数fillTank(),而电力动力源有一个成员函数chargeBatteries():

class PowerSource
{
public:
	virtual void printType() = 0;
};

class GasolinePowerSource : public PowerSource
{
public:
	void printType() override { println("GasolinePowerSource"); }
	virtual void fillTank() { println("Gasoline tank filled up."); }
};

class ElectricalPowerSource : public PowerSource
{
public:
	void printType() override { println("ElectricalPowerSource"); }
	virtual void chargeBatteries() { println("Batteries charged."); }
};

        现在假定Car有一个虚成员函数叫做getFilledUpPowerSource()返回一个指向特定汽车的“加满”动力源的引用:

class Car
{
public:
	virtual PowerSource& getFilledUpPowerSource() = 0;
};

        这是一个干净的virtual,抽象的成员函数,因为它只是对提供一个真实的具体的继承类的实现有效。既然GaslinePowerSource是PowerSource,那么GasolineCar类可以实现该成员函数如下:

class GasolineCar : public Car
{
public:
	PowerSource& getFilledUpPowerSource() override
	{
		m_engine.fillTank();
		return m_engine;
	}
private:
	GasolinePowerSource m_engine;
};

        ElectricalCar可以实现如下:

class ElectricalCar : public Car
{
public:
	PowerSource& getFilledUpPowerSource() override
	{
		m_engine.chargeBatteries();
		return m_engine;
	}
private:
	ElectricalPowerSource m_engine;
};

        这些类可以测试如下:

	GasolineCar gc;
	gc.getFilledUpPowerSource().printType();

	println("");

	ElectricalCar ev;
	ev.getFilledUpPowerSource().printType();

        输出如下:

Gasoline tank filled up.
GasolinePowerSource

Batteries charged.
ElectricalPowerSource

        这个实现是没有问题,然而,因为知道GaslineCar的getfilledUpPowerSource()成员函数总是返回GasolinePowerSource,而ElectricalCar总是ElectricalPowerSource,可以通过修改返回类型显示这个事实给到这些类的潜在用户,如下所示:

class GasolineCar : public Car
{
public:
	//PowerSource& getFilledUpPowerSource() override
	GasolinePowerSource& getFilledUpPowerSource() override
	{
		m_engine.fillTank();
		return m_engine;
	}
private:
	GasolinePowerSource m_engine;
};

class ElectricalCar : public Car
{
public:
	//PowerSource& getFilledUpPowerSource() override
	ElectricalPowerSource& getFilledUpPowerSource() override
	{
		m_engine.chargeBatteries();
		return m_engine;
	}
private:
	ElectricalPowerSource m_engine;
};

        指出能否修改重载成员函数的好的方法是考虑既有代码是否仍然可以运行;这叫做Liskov替换原则(LSP)。在前面的例子中,修改返回类型是没有问题的,因为假定getFilledUpPowerSource()成员函数总是返回PowerSource的代码仍然可以编译并且运行良好。因为ElectircalPowerSource与GasolinePowerSource都是PowerSource,任何成员函数被在getFilledUpPowerSource()的结果上调用都返回一个PowerSource仍然可以被在getFilledUpPowerSource()的结果上被调用返回ElectricalPowerSource或GasolinePowerSource。

        例如,不能修改完全不相关的东东的返回类型,例如int&。下面的代码编译不会成功:

class ElectricalCar : public Car
{
public:
    int& getFilledUpPowerSource() override // Error!
    { /* omitted for brevity */ }
};

        这会产生一个如下的编译错误,如下:

'ElectricalCar::getFilledUpPowerSource': overriding virtual function return type
differs and is not covariant from 'Car::getFilledUpPowerSource'

        这个例子使用了PowerSource的引用,没有使用智能指针。当使用如shared_ptr这样的返回类型时,修改返回类型是不灵的。假定Car::getFilledUpPowerSource()返回一个shared_ptr<PowerSource>。在这种情况下,不能将ElectricalCar::getFilledUpPowerSource()的返回类型修改为shared_ptr<ElectricalPowersource>。原因是shared_ptr是一个类模板。生成了两个shared_prt类模板的实例,shared_ptr<PowerSource>与shared_ptr<ElectricalPowerSource>。两个实例是完全不同的类型,且不可能彼此相关。不能改变重载成员函数的返回类型为一个完全不同的类型。

猜你喜欢

转载自blog.csdn.net/weixin_71738303/article/details/143247802