以下代码打印“我是B!”。这有点奇怪,因为B::foo()
是私有的。关于A* ptr
我们可以说它的静态类型是A
(foo
是公开的),其动态类型是B
(foo
是私有的)。所以我可以通过指向foo
的指针调用A
。但是这样我可以访问B
中的私有函数。它可以被视为封装违规吗?
由于访问限定符不是类方法签名的一部分,因此可能导致这种奇怪的情况。为什么在覆盖虚函数时不考虑C ++访问限定符?我可以禁止此类案件吗?这个决定背后有什么设计原则?
#include <iostream>
class A
{
public:
virtual void foo()
{
std::cout << "I'm A!\n";
};
};
class B: public A
{
private:
void foo() override
{
std::cout << "I'm B!\n";
};
};
int main()
{
A* ptr;
B b;
ptr = &b;
ptr->foo();
}
答案 0 :(得分:2)
访问限定符(如public
,private
等)是编译时功能,而动态多态是运行时功能。
当调用private
函数的virtual
覆盖时,您认为应该在运行时发生什么?例外?
是否可以将其视为封装违规?
不,因为接口已经通过继承发布,所以不是。
在派生类中使用public virtual
函数覆盖基类中的private
函数是完全正确的(并且可能是预期的)。
答案 1 :(得分:2)
你有多个问题,所以我会尝试逐个回答。
因为在所有重载决策之后编译器会考虑访问限定符。 这种行为由标准规定。
例如,请参阅on cppreference:
成员访问权限不会影响可见性:私有和私有继承成员的名称可见并通过重载解析来考虑,仍然会考虑对不可访问的基类进行隐式转换,等等。成员访问检查是任何后续步骤给定的语言结构被解释。这条规则的目的是用公共替换任何私人从不改变程序的行为。
下一段描述了您的示例演示的行为:
使用用于表示调用成员函数的对象的表达式类型,在调用点检查虚函数名称的访问规则。最终覆盖者的访问权将被忽略。
另请参阅this answer中列出的操作顺序。
没有
我认为你永远不会这样做,因为这种行为没有任何违法行为。
只是为了澄清:通过“决定”这里我暗示了编译器在重载解析后检查访问限定符的处方。 简短的回答:在您更改代码时防止出现意外。
有关详细信息,我们假设您正在开发一些CoolClass
,看起来像这样
class CoolClass {
public:
void doCoolStuff(int coolId); // your class interface
private:
void doCoolStuff(double coolValue); // auxiliary method used by the public one
};
假设编译器可以基于公共/私有说明符执行重载解析。然后,以下代码将成功编译:
CoolClass cc;
cc.doCoolStuff(3.14); // invokes CoolClass::doCoolStuff(int)
// yes, this would raise the warning, but it can be ignored or suppressed
然后在某些时候您发现您的私有成员函数实际上对类客户端有用并将其移动到“公共”区域。这会自动更改预先存在的客户端代码的行为,因为它现在会调用CoolClass::doCoolStuff(double)
。
因此,应用访问限定符的规则是以不允许这种情况的方式编写的,因此您将在一开始就得到“模糊调用”编译器错误。由于同样的原因,虚函数不是特例(参见this answer)。
不是真的。 通过将指向您的类的指针转换为指向其基类的指针,您实际上是在说:“在此我想使用此对象B,就好像它是一个对象A” - 这是完全合法的,因为继承意味着“原样” “关系。
所以问题是,你的例子可以被视为违反基类规定的合同吗?似乎是的,它可以。
有关替代解释,请参阅this question的答案。
不要误解我的意思,所有这些并不意味着你不应该使用私有虚函数。相反,通常被认为是一种良好的做法,请参阅this thread。但他们应该是基层的私人。再说一次,最重要的是,你不应该使用私人虚拟功能来破坏公共合同。
P.P.S。 ...除非你故意强迫客户端通过指向接口/基类的指针来使用你的类。但是有更好的方法,我认为对这些问题的讨论超出了这个问题的范围。