3.2、基类成员函数被重载
当通过指定名字与一系列的参数重载成员函数时,编译器隐式地隐藏了所有基类中同名的其它实例。这个主意就是如果用一个给定的名字重载了一个成员函数,可能想要重载那个名字的所有成员函数,但是只是忘记了,这应该会被认为是一个错误。你这样想是有道理的--为什么要改变一个成员函数的某些重载,其他的却不改?考虑下面的Derived类,它重载了一个成员函数而没有重载与其相关的过载的兄弟姐妹:
class Base
{
public:
virtual ~Base() = default;
virtual void overload() { println("Base's overload()"); }
virtual void overload(int i) { println("Base's overload(int i)"); }
};
class Derived : public Base
{
public:
//using Base::overload;
void overload() override { println("Derived's overload()"); }
};
如果尝试在Derived对象上调用带有int参数的overload()版本,代码编译不成功,因为它没有显式重载。
Derived myDerived;
myDerived.overload(2); // Error! No matching member function for overload(int).
然而,访问Derived对象的成员函数的版本是可能的。要做的是一个指向Base对象的指针或引用。
Derived myDerived;
Base& ref{ myDerived };
ref.overload(7);
没有实现的过载成员函数的隐藏在c++内部只是表象。显式声明为继承类实例的对象不会让成员函数可用,但是可以通过简单地转化为基类把它们找回来。
当你确实只想修改其中一个时,using声明可以用于挽救你于重载所有的过载的麻烦。在下面的代码中,Derived类定义使用了一个Base的overload()的一个版本,显式重载了另外一个:
class Derived : public Base
{
public:
using Base::overload;
void overload() override { println("Derived's overload()"); }
};
using声明有一定的风险。假定给Base添加了第三个overload()成员函数,它应该在Derived中被重载。这不会被当作错误检测到,因为使用using声明,Derived类的设计者显式地说明,“我愿意接受所有父类这个成员函数的其它过载。”
警告:为了避免不清晰的问题,当在基类中重载成员函数时,也要重载那个成员函数的所有过载。
3.3、基类成员函数是私有的
重载私有的成员函数绝对没有问题。记住成员函数的访问指示符决定了谁能够调用该成员函数。就是因为继承类不能调用父类的私有的成员函数并不意味着它不能重载它们。实际上,模板成员函数模式在c++中是一个通用的模式,通过重载私有成员函数实现。它允许继承类定义它们自己的“独特性”,在基类中引用。记住,例如,Java与C#只是允许重载公共的与受保护的成员函数,私有的成员函数不行。
例如,下面的类是汽车模拟器的一部分,预测了基于它的油箱与所剩的燃料的英里数。getMilesLeft()成员函数就是模板成员函数。通常模板成员函数不是virtual。它们典型定义了一些算法的骨架在基类中,调用virtual成员函数来查询信息。继承类然后重载这些virtual成员函数来改变算法的方面,而不用修改基类自身的算法。
export class MilesEstimator
{
public:
virtual ~MilesEstimator() = default;
int getMilesLeft() const
{
return getMilesPerGallon() * getGallonsLeft();
}
virtual void setGallonsLeft(int gallons)
{
m_gallonsLeft = gallons;
}
virtual int getGallonsLeft() const
{
return m_gallonsLeft;
}
private:
int m_gallonsLeft{ 0 };
virtual int getMilesPerGallon() const
{
return 20;
}
};
getMilesLeft()成员函数执行了基于它自身的两个成员函数:getGallonsLeft()是公共的,和getMilesperGallon()是私有的,的结果的计算。下面的代码使用了MilesEstimator来计算有2加仑汽油还能开多少英里:
MilesEstimator myMilesEstimator;
myMilesEstimator.setGallonsLeft(2);
println("Normal estimator can go {} more miles.",
myMilesEstimator.getMilesLeft());
代码的输出如下:
Normal estimator can go 40 more miles.
为了让模拟器更有趣,可能会想引入不同类型的交通工具,可能是一辆更高效的汽车。现存的MilesEstimator假定所有的汽车每加仑能跑20英里,但是这个值是从一个单独的特定成员函数返回来的,所以继承类可以重载它。重载的类如下:
export class EfficientCarMilesEstimator : public MilesEstimator
{
private:
int getMilesPerGallon() const override
{
return 35;
}
};
通过重载这个私有的成员函数,新的类完全改变了基类中既有的,没有改变的,公共的成员函数的行为。基类中的getMilesLeft()成员函数自动调用private getMilesPerGallon()成员函数的重载版本。使用新类的例子如下:
EfficientCarMilesEstimator myEstimator;
myEstimator.setGallonsLeft(2);
println("Efficient estimator can go {} more miles.",
myEstimator.getMilesLeft());
这一次,输出反映了重载的功能:
Efficient estimator can go 70 more miles.
注意:重载私有的与受保护的成员函数是一个好的方式来改变类的某些属性而不需要大的改造。