我已经看到它声明C ++隐藏了名称以减少脆弱的基类问题。但是,我绝对不知道这有多大帮助。如果基类引入了以前不存在的函数或重载,它可能与派生类引入的函数或对全局函数或成员函数的非限定调用冲突 - 但我没有看到的是重载的不同之处。为什么要将虚函数的重载与其他函数区别对待?
编辑:让我再向您展示一下我正在谈论的内容。
struct base {
virtual void foo();
virtual void foo(int);
virtual void bar();
virtual ~base();
};
struct derived : base {
virtual void foo();
};
int main() {
derived d;
d.foo(1); // Error- foo(int) is hidden
d.bar(); // Fine- calls base::bar()
}
此处,foo(int)
与bar()
的处理方式不同,因为它是一个过载。
答案 0 :(得分:7)
我假设通过“脆弱的基类”,你的意思是一种情况,基类的更改可能会破坏使用派生类的代码(这是我在维基百科上找到的定义)。我不确定虚拟函数与此有什么关系,但我可以解释隐藏有助于避免这个问题。请考虑以下事项:
struct A {};
struct B : public A
{
void f(float);
};
void do_stuff()
{
B b;
b.f(3);
}
do_stuff
中的函数调用B::f(float)
。
现在假设某人修改了基类,并添加了一个函数void f(int);
。不隐藏,这将是main
中函数参数的更好匹配;您已经更改了do_stuff
的行为(如果新函数是公共的),或者导致编译错误(如果它是私有的),而不更改do_stuff
或其任何直接依赖项。通过隐藏,您没有更改行为,只有在使用using
声明明确禁用隐藏时才能进行此类破坏。
答案 1 :(得分:2)
我不认为虚函数的重载与常规函数的重载有任何不同。可能会有一个副作用。
假设我们有3层层次结构:
struct Base {};
struct Derived: Base { void foo(int i); };
struct Top: Derived { void foo(int i); }; // hides Derived::foo
当我写:
void bar(Derived& d) { d.foo(3); }
将调用静态解析为Derived::foo
,无论d
可能具有的真实(运行时)类型。
但是,如果我在virtual void foo(int i);
中引入Base
,那么一切都会发生变化。突然Derived::foo
和Top::foo
成为覆盖,而不仅仅是超载,将名称隐藏在各自的基类中。
这意味着现在d.foo(3);
静态解析不是直接解决方法调用,而是解析为虚拟调度。
因此Top top; bar(top)
将调用Top::foo
(通过虚拟调度),之前调用Derived::foo
。
可能不太可取。它可以通过明确限定调用d.Derived::foo(3);
来修复,但肯定是一个不幸的副作用。
当然,这主要是一个设计问题。它只会在签名兼容的情况下发生,否则我们将隐藏名称,并且不会覆盖;因此,有人可能会争辩说,对非虚拟功能进行“潜在”覆盖无论如何都会引发麻烦(不知道是否存在任何警告,它可以保证一个,以防止出现这种情况)。
注意:如果我们删除Top,那么引入新的虚方法是完全没问题的,因为所有旧调用都已经由Derived :: foo处理,因此只有新代码可能会受到影响
在基类中引入新的virtual
方法时要记住这一点,特别是当受影响的代码未知时(传递给客户端的库)。
请注意,C ++ 0x具有override
属性,用于检查方法是否真正覆盖了基本虚拟;虽然它不能解决当前的问题,但是在未来我们可能会想到编译器会对“意外”覆盖进行警告(即,覆盖未标记为覆盖),在这种情况下,这样的问题可以在编译时被捕获 引入虚拟方法。
答案 2 :(得分:2)
C ++的设计与演变,Bjarne Stroustrup Addison-Weslay,1994年第3.5.3页第77,78页,B.S。解释了派生类中的名称隐藏其基类中同名的所有定义的规则是旧的,并且可以使用Classes返回C。介绍时,B.S。将其视为范围规则的明显结果(对于嵌套的代码块或嵌套命名空间,它们是相同的 - 即使后面引入了命名空间)。它与重载规则交互的可取性(重载集不包含在基类中定义的函数,也不包含在封闭块中 - 现在无害,因为块中的声明函数是老式的),也不包含偶尔出现问题的命名空间罢工也一直在争论,以至于G ++实现了允许重载的替代规则和BS他认为当前的规则有助于防止出现错误(例如,受到g ++实际问题的启发)
class X {
int x;
public:
virtual void copy(X* p) { x = p->x; }
};
class XX: public X {
int xx;
public:
virtual void copy(XX* p) { xx = p->xx; X::copy(p); }
};
void f(X a, XX b)
{
a.copy(&b); // ok: copy X part of b
b.copy(&a); // error: copy(X*) is hidden by copy(XX*)
}
然后B.S.继续
回想起来,我怀疑2.0中引入的重载规则可能已经能够处理这种情况。考虑通话
b.copy(&a)
。变量b
是隐式参数XX::copy
的精确类型匹配,但需要标准转换才能匹配X::copy
。另一方面,变量a
与X::copy
的显式参数完全匹配,但需要标准转换才能匹配XX:copy
。因此,如果允许重载,则调用将是错误的,因为它是不明确的。
但我没有看到歧义在哪里。在我看来,B.S。忽略了&a
无法隐式转换为XX*
这一事实,因此只考虑X::copy
。
确实尝试使用免费(朋友)功能
void copy(X* t, X* p) { t->x = p->x; }
void copy(XX* t, XX* p) { t-xx = p->xx; copy((X*)t, (X*)p); }
我没有遇到当前编译器的歧义错误,我不知道 Annotated C ++ Reference Manual 中的规则如何在这里起作用。