当非常量方法为私有时,为什么不调用公共常量方法?

考虑下面的代码:

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();
}

编译器错误是:

Error: ‘ void A: : foo ()’is private‘错误: ‘ void A: : foo ()’是私有的’。

但是当我删除私人信息的时候,它就正常工作了。为什么当非 const 方法是私有的时候不调用 public const 方法?

换句话说,为什么重载解析优先于访问控制?真奇怪。你认为这是一致的吗?我的代码工作,然后我添加一个方法,我的工作代码根本不编译。

8140 次浏览

因为 main函数中的变量 a没有声明为 const

对常数对象调用常数成员函数。

由于隐式 this指针是非 const的,编译器将首先检查在 const版本之前是否存在非 const版本的函数。

如果您显式地标记非 const的一个 private,那么解析将失败,编译器将不会继续搜索。

In this call:

a.foo();

There is always an implicit this pointer available in every member function. And the const qualification of this is taken from the calling reference/object. The above call is treated by the compiler as:

A::foo(a);

但是你有两个 A::foo的声明,也就是 受到的待遇:

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

通过重载分辨率,第一个将被选择为非常数 this,第二个将被选择为 const this。如果删除第一个,第二个将同时绑定到 constnon-const this

重载解决后,选择最佳可行的功能,来访问控制。由于您将所选重载的访问权限指定为 private,编译器将随后发出抱怨。

标准是这样规定的:

[ class.access/4] : ... 在函数名重载的情况下,访问控制应用于 通过重载分辨率选择的函数... 。

但如果你这样做:

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

然后,只有 const过载将适合。

当您调用 a.foo();时,编译器将通过重载解析来找到要使用的最佳函数。当它构建重载集时,它会找到

void foo() const

还有

void foo()

Now, since a is not const, the non-const version is the best match, so the compiler picks void foo(). Then the access restrictions are put in place and you get a compiler error, since void foo() is private.

记住,在重载分辨率中,它不是“找到最好的可用功能”。它是“找到最好的功能,并尝试使用它”。如果由于访问限制或被删除而无法访问,那么将得到一个编译器错误。

换句话说,为什么重载解析优先于访问控制?

让我们看看:

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将被打印。在大型代码库中可能很难追踪到这一点。由于访问控制是在重载解析之后进行的,所以我得到了一个很好的编译器错误,告诉我我想要它调用的函数不能被调用,而且我可以更容易地找到 bug。

访问控制(publicprotectedprivate)不影响重载分辨率。编译器选择 void foo()是因为它是最佳匹配。事实上,无法访问并不能改变这一点。删除它只留下 void foo() const,这是最佳(即,唯一)匹配。

这涉及到 C + + 中一个相当基本的设计决策。

当查找函数以满足调用时,编译器执行如下搜索:

  1. 它搜索找到第一个 1作用域,在该作用域中有具有该名称的 什么的

  2. 编译器在该作用域中查找具有该名称的函数(或函数等)。

  3. 然后编译器执行重载解析,以便在它找到的候选对象中找到最佳候选对象(无论它们是否可访问)。

  4. 最后,编译器检查所选函数是否可访问。

由于这种顺序,是的,编译器可能会选择一个不可访问的重载,即使还有另一个可访问的重载(但在重载解析期间没有选择)。

As to whether it would be 有可能 to do things differently: yes, it's undoubtedly possible. It would definitely lead to quite a different language than C++ though. It turns out that a lot of seemingly rather minor decisions can have ramifications that affect a lot more than might be initially obvious.


  1. “ First”本身可能有点复杂,特别是当/如果涉及到模板时,因为它们可能导致两阶段查找,这意味着在进行搜索时有两个完全独立的“ root”。基本的的想法非常简单: 从最小的封闭范围开始,一路向外到越来越大的封闭范围。

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

  1. 找到所有可行的功能。
  2. 选择最佳可行的功能。
  3. 如果没有确切的最佳可行函数,或者实际上不能调用最佳可行函数(由于访问冲突或函数是 deleted) ,则失败。

(3)发生在(2)之后。这一点非常重要,因为否则函数 deleted 或 private将变得毫无意义,而且更难推理。

In this case:

  1. 可行的功能是 A::foo()A::foo() const
  2. 最好的可行函数是 A::foo(),因为后者涉及隐式 this参数的限定转换。
  3. But A::foo() is private and you don't have access to it, hence the code is ill-formed.

访问说明符永远不会影响名称查找和函数调用解析。在编译器检查调用是否应该触发访问冲突之前选择该函数。

这样,如果您更改了访问说明符,那么在编译时如果现有代码中存在冲突,您将收到警告; 如果在函数调用解析中考虑了隐私,那么您的程序的行为可能会悄悄地发生更改。

The technical reason has been answered by other answers. I'll only focus on this question:

换句话说,为什么重载解析优先于访问控制?真奇怪。你认为这是一致的吗?我的代码工作,然后我添加一个方法,我的工作代码根本不编译。

语言就是这样设计的。目的是尽可能地调用最好的可行过载。如果失败,将触发一个错误,提醒您再次考虑该设计。

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

归根结底,这归结为标准中的断言: 在执行超载解析时,不应考虑可访问性。这种说法可以在 [比赛结束]第3条中找到:

... 当重载解析成功,并且在使用它的上下文中无法访问最佳可行函数(子句[ class.access ])时,程序就是格式不正确的。

以及同一节第1条中的 注意:

[注意: 通过重载解析选择的函数不能保证适用于上下文。其他限制,例如函数的可访问性,可能使其在调用上下文中的使用格式不正确。ー尾注]

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

  1. 它可以防止由于更改重载候选对象的可访问性而导致意外的行为更改(相反,将发生编译错误)。
  2. 它从重载解析过程中移除了上下文相关性(即,无论是在类内部还是在类外部,重载解析都会得到相同的结果)。

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

Section 2.10 of Stroustrup 的 C + + 设计与演进 has a passage on this where he discusses the following example

int a; // global a


class X {
private:
int a; // member X::a
};


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

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

然后他说,他不记得这是由于显式设计还是预处理器技术的副作用,预处理器技术用于实现 C 和标准 C + + 的前身。

How is this related to your example? Basically because the Standard made overload resolution conform to the general rule that name lookup comes before access control.

10.2成员名查找[ class.Member. lookup ]

1成员名称查找确定名称(id 表达式)的含义 在类范围(3.3.7)中。名称查找可能导致歧义,在 对于 id 表达式,名称 查找从这个类的作用域开始; 对于限定 id,名称为 lookup begins in the scope of the nestedname- specifier. Name lookup 发生在访问控制之前 (3.4,第11条)

如果明确地找到重载函数的名称, 重载分辨率(13.3)也发生在访问控制 之前。 通常可以通过用类限定一个名称来解决歧义 姓名。