为什么在非consttypes的私有方法中不调用公共const方法?

考虑这个代码:

struct A { void foo() const { std::cout << "const" << std::endl; } private: void foo() { std::cout << "non - const" << std::endl; } }; int main() { A a; a.foo(); } 

编译器错误是:

错误:'void A :: foo()'是私人的。

但是,当我删除私人的只是工作。 为什么在非consttypes的私有方法时不调用公共const方法?

换句话说,为什么在访问控制之前重载parsing? 这很奇怪。 你认为这是一致的吗? 我的代码工作,然后我添加一个方法,我的工作代码根本不编译。

当你调用a.foo(); ,编译器通过重载parsing来find最好的函数来使用。 当它build立超载设置它发现

 void foo() const 

 void foo() 

现在,由于a不是const ,所以非const版本是最好的匹配,所以编译器selectvoid foo() 。 然后访问限制放在适当的地方,你会得到一个编译器错误,因为void foo()是私有的。

记住,在重载分辨率下,它不是“find最好的可用函数”。 它是“find最好的function,并尝试使用它”。 如果不能因为访问限制或被删除,那么你会得到一个编译器错误。

换句话说,为什么在访问控制之前重载parsing?

那么,让我们看看:

 struct Base { void foo() { std::cout << "Base\n"; } }; struct Derived : Base { void foo() { std::cout << "Derived\n"; } }; struct Foo { void foo(Base * b) { b->foo(); } private: void foo(Derived * d) { d->foo(); } }; int main() { Derived d; Foo f; f.foo(&d); } 

现在让我们说,我并没有真正意义上使void foo(Derived * d)私有。 如果访问控制第一个然后这个程序将编译和运行和Base将被打印。 在一个庞大的代码库中,这可能很难find。 由于访问控制是在重载parsing之后产生的,所以我得到了一个很好的编译器错误,告诉我我想调用的函数不能被调用,而且我可以更容易地find这个错误。

最终,这归结于标准中的断言,即在执行重载分辨率时不应考虑可访问性 。 这个断言可以在[over.match]第3条中find:

…当重载parsing成功,并且最佳可行函数在其使用的上下文中不可访问(Clause [class.access])时,该程序是格式不正确的。

以及同一条第1款的注释

[注意:通过重载分辨率select的function不能保证适合上下文。 其他的限制,比如函数的可访问性,可以使其在调用环境中使用不当。 – 结束注意]

至于为什么,我可以想到一些可能的动机:

  1. 它可以防止由于更改过载候选的可访问性而导致的意外行为更改(而是会发生编译错误)。
  2. 它从重载parsing过程中消除了上下文依赖(即重载parsing无论在课堂内还是课外都有相同的结果)。

假设访问控制是在重载parsing之前产生的。 实际上,这意味着public/protected/private控制的可见性而不是可访问性。

Stroustrup的C ++devise和演化的第2.10节有一段话来讨论下面的例子

 int a; // global a class X { private: int a; // member X::a }; class XX : public X { void f() { a = 1; } // which a? }; 

Stroustrup提到,当前规则(可访问性之前的可见性)的好处在于,将(暂时)将public内部的class X private (例如,出于debugging的目的)是上述程序的意义没有变化即在这两种情况下试图访问X::a ,这在上例中给出访问错误)。 如果public/protected/private将控制可见性,程序的含义将会改变(全局a将被调用private ,否则X::a )。

他然后说,他不记得是否是通过明确的devise或用于实现具有标准C ++的Classess前任的C的预处理器技术的副作用。

这与你的例子有什么关系? 基本上是因为标准做出的重载parsing符合名称查找来访问控制之前的一般规则。

10.2会员名称查找[class.member.lookup]

1成员名称查找确定类范围(3.3.7)中名称(id-expression)的含义。 名称查询可能会导致模糊,在这种情况下该程序是格式不正确的。 对于一个idexpression式,名称查找从这个类的范围开始; 对于合格的id,名称查找从嵌套名称说明符的范围开始。 名称查找发生在访问控制之前 (3.4,第11章)。

8如果明确地find重载函数的名称,则在访问控制之前也会发生重载parsing(13.3) 。 歧义常常可以通过使用类名来限定一个名字来解决。

由于这个隐含的指针是非const ,因此编译器会首先在const版本之前检查const的非const版本的存在。

如果明确地将非常量标记为private则parsing将失败,编译器将不会继续search。

记住发生的事情的顺序是很重要的,这是:

  1. find所有可行的function。
  2. select最好的可行function。
  3. 如果没有一个最好的可行性,或者如果实际上不能调用最好的可行函数(由于访问冲突或函数被delete ),则失败。

(3)发生在(2)之后。 这是非常重要的,因为否则使functiondeleteprivate将成为一种毫无意义,更难以推理。

在这种情况下:

  1. 可行的函数是A::foo()A::foo() const
  2. 最好的可行函数是A::foo()因为后者涉及隐式this参数的限定转换。
  3. 但是A::foo()private ,你不能访问它,因此代码是格式不对的。

这归结于C ++中一个相当基本的devise决定。

当查找函数来满足一个调用时,编译器执行这样的search:

  1. 它search以find具有该名称的第一个范围。

  2. 编译器在该范围内find具有该名称的所有函数(或函子等)。

  3. 然后,编译器会重载parsing,findfind的最佳候选(无论是否可访问)。

  4. 最后,编译器检查所​​select的function是否可访问。

由于这种sorting,是的,编译器可能会select一个无法访问的重载,即使有另一个可以访问的重载(但在重载parsing期间没有select)。

至于是否有可能做不同的事情:是的,这无疑是可能的。 但肯定会导致与C ++完全不同的语言。 事实certificate,很多似乎很小的决定可能会产生比最初显而易见的影响更大的后果。


  1. “First”本身可能有点复杂,特别是在涉及到模板的情况下,因为它们可以导致两阶段查找,这意味着从search开始就有两个完全独立的“根”。 基本的想法非常简单:从最小的封闭范围开始,向外扩大到更大的封闭范围。

访问控制( publicprotectedprivate )不会影响重载parsing。 编译器selectvoid foo()是因为它是最好的匹配。 它不可访问的事实并没有改变这一点。 删除它只留下void foo() const ,这是最好的(即唯一)匹配。

在这个电话中:

 a.foo(); 

每个成员函数都有一个隐含的指针可用。 而这个const限定是从调用引用/对象中获取的。 上面的调用编译器视为:

 A::foo(a); 

但是你有两个A::foo声明, A::foo声明是这样处理的

 A::foo(A* ); A::foo(A const* ); 

通过重载parsing,第一个将被选中为非const,第二个将被选中为const this 。 如果你删除第一个,第二个将绑定到constnon-const this

在重载分辨率select最佳可行function后,来访问控制。 由于您将所选重载的访问指定为private ,编译器会发出抱怨。

标准是这样说的:

[class.access / 4] : …在重载函数名称的情况下,访问控制应用于通过重载parsingselect的函数。

但是,如果你这样做:

 A a; const A& ac = a; ac.foo(); 

那么,只有const超载将适合。

技术原因已被其他答案回答。 我只专注于这个问题:

换句话说,为什么在访问控制之前重载parsing? 这很奇怪。 你认为这是一致的吗? 我的代码工作,然后我添加一个方法,我的工作代码根本不编译。

这就是语言的devise。 目的是尽可能地调用最好的可行超载。 如果失败,会触发错误提醒您再次考虑devise。

另一方面,假设你的代码编译和调用的const成员函数运行良好。 有一天,有人(也许是你自己)决定把非常量成员函数的可访问性从private变为public 。 然后,行为会改变,没有任何编译错误! 这将是一个惊喜

因为main函数中的variablesa没有声明为const

常量成员函数在常量对象上调用。

访问说明符不会影响名称查找和函数调用parsing。 在编译器检查调用是否触发访问冲突之前select该函数。

这样,如果您更改访问说明符,则在编译时会在现有代码中存在违规的情况下收到警报。 如果function调用解决scheme考虑了隐私,那么程序的行为可能会默默地改变。