这个主题和继承关系不大,主要是对作用域的理解和把握。
还是通过代码来看:
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
};
class Derived : public Base {
public:
virtual void mf1();
void mf3();
void mf4();
};
int main()
{
Derived d;
int x = 1;
d.mf1();//没问题,调用Derived::mf1()
d.mf1(x);//错误,Derived::mf1()遮掩了Base::mf1函数
d.mf2();//没问题,调用Derived::mf2()
d.mf3();//没问题,调用Derived::mf3()
d.mf3(x);//错误,Derived::mf1()遮掩了Base::mf3函数
return 0;
}
上述代码中,子类中的mf1和mf3遮掩了基类中的所有名为mf1和mf3的函数,于是,Base::mf1和Base::mf3就不再被Derived继承。
这样做的好处在于避免在程序库或应用框架内建立新的子类时,附带地从疏远的基类继承重载函数。像例子中,避免了基类中mf1和mf3函数的重载函数的继承。
而pubic继承,子类与基类是“is-a”的关系,意味着子类拥有基类的所有东西,如果使用public继承,又不继承基类中的重载函数,那就违反了public继承的意义。
为了实现public继承中子类与基类的“is-a”的关系,有两种方案:
1. 方案一:使用using声明式实现:
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
};
class Derived : public Base {
public:
using Base::mf1; //使用using声明式使Base::mf1在子类中可见
using Base::mf3; //使用using声明式使Base::mf3在子类中可见
virtual void mf1();
void mf3();
void mf4();
};
int main()
{
Derived d;
int x = 1;
d.mf1();//没问题,调用Derived::mf1()
d.mf1(x);//没问题,调用Base::mf1函数
d.mf2();//没问题,调用Derived::mf2()
d.mf3();//没问题,调用Derived::mf3()
d.mf3(x);//没问题,调用Base::mf3函数
return 0;
}
方案一解决了由于在子类中重新定义函数,遮掩了基类重载函数的问题。using声明式使得基类中的函数在子类作用域中都可见。但是,我们可能并不总是希望继承基类所有的函数都在子类可见,这样的需求该如何满足?
2. 方案二使用private继承,并在继承中使用了转交函数(forwarding function)来解决上述的问题:
class Base {
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
};
class Derived : private Base {
public:
virtual void mf1() {
Base::mf1();//转交函数,仅继承基类无参的mf1函数
}
};
int main()
{
Derived d;
int x = 1;
d.mf1();//没问题,调用Derived::mf1()
d.mf1(x);//错误,Base::mf1函数被遮掩
return 0;
}
在子类的mf1函数,将mf1函数需要执行的任务交给了Base类的mf1函数来执行。
而在其中仅调用了Base的mf1函数的无参函数,达到了遮掩mf1有参函数的目标。
3. 总结:
- 名称遮掩的问题本质是对作用域的理解,能够很好理解作用域,名称遮掩的问题也不是难事。
- 解决名称遮掩问题的方式有using声明式和转交函数方法,可根据实际应用场景选择合适的方案。
- 有关转交函数的问题可查阅《Exceptional C++》的条款46,有对转交函数的详细介绍