为什么在非 const 方法为私有时不调用 public const 方法?

Why is a public const method not called when the non-const one is private?

考虑这段代码:

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`.

但是当我删除私人的时,它就起作用了。为什么在非 const 方法为私有时不调用 public const 方法?

换句话说,为什么重载决议先于访问控制?这很奇怪。你觉得一致吗?我的代码运行正常,然后我添加了一个方法,但我的运行代码根本无法编译。

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

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

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

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

在这个调用中:

a.foo();

在每个成员函数中总是有一个隐式的 this 指针可用。而thisconst资格取自调用reference/object。上述调用 被编译器 视为:

A::foo(a);

但是你有两个 A::foo 的声明,它们 被视为 :

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

通过重载解析,第一个将 selected 用于非常量 this,第二个将 selected 用于 const this。如果删除第一个,第二个将同时绑定到 constnon-const this.

重载解析为 select 最佳可行函数后,访问控制来了。由于您将对所选重载的访问指定为 private,因此编译器会报错。

标准是这样说的:

[class.access/4]: ...In the case of overloaded function names, access control is applied to the function selected by overload resolution....

但是如果你这样做:

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

那么,只有 const 重载是合适的。

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

void foo() const

void foo()

现在,由于 a 不是 const,非 const 版本是最佳匹配,因此编译器选择 void foo()。然后访问限制就位,你会得到一个编译器错误,因为 void foo() 是私有的。

记住,在重载决议中它不是 'find the best usable function'。是'find the best function and try to use it'。如果由于访问限制或被删除而不能,则会出现编译错误。

In other words why does overload resolution comes before access control?

好吧,让我们看看:

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

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

这归结为 C++ 中一个相当基本的设计决策。

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

  1. 它会搜索第一个1 作用域,其中某物 具有该名称。

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

  3. 然后编译器进行重载决策以在它找到的候选者中找到最佳候选者(无论它们是否可访问)。

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

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

至于是否可能做不同的事情:是的,这无疑是可能的。不过,它肯定会导致与 C++ 完全不同的语言。事实证明,许多看似微不足道的决定可能会产生比最初显而易见的影响更大的后果。


  1. "First" 本身可能有点复杂,尤其是涉及 when/if 模板,因为它们会导致两阶段查找,这意味着有两个完全独立的 "roots"从进行搜索时开始。 基本 想法非常简单:从最小的封闭范围开始,逐步向外扩展到越来越大的封闭范围。

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

  1. 找出所有可行的函数。
  2. 选择最佳可行函数。
  3. 如果不存在最佳可行函数,或者如果您实际上无法调用最佳可行函数(由于访问冲突或函数为 deleted),则失败。

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

在这种情况下:

  1. 可行的函数是A::foo()A::foo() const
  2. 最可行的函数是 A::foo(),因为后者涉及对隐式 this 参数的限定转换。
  3. 但是 A::foo()private 并且您无权访问它,因此代码格式错误。

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

这样,如果您更改访问说明符,如果现有代码存在违规,您将在编译时收到警报;如果在函数调用解析中考虑到隐私,您的程序的行为可能会悄无声息地改变。

技术原因,其他回答已经回答过了。我只关注这个问题:

In other words why overload resolution comes before access control? This is strange. Do you think it is consistent? My code works and then I add a method and my working code does not compile at all.

这就是语言的设计方式。目的是尝试尽可能调用最佳可行的过载。如果失败,会触发错误提醒重新考虑设计。

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

最终这归结为标准中的断言,即在执行重载解析时 不应考虑可访问性 。此断言可在 [over.match] 条款 3:

中找到

... When overload resolution succeeds, and the best viable function is not accessible (Clause [class.access]) in the context in which it is used, the program is ill-formed.

以及同一节第 1 条中的

[ Note: The function selected by overload resolution is not guaranteed to be appropriate for the context. Other restrictions, such as the accessibility of the function, can make its use in the calling context ill-formed. — end note ]

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

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

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

Design and Evolution of C++ by Stroustrup 的第 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 提到当前规则的一个好处(可见性先于可访问性)是(暂时)将 class X 内的 private 更改为 public(例如,为了调试目的) 是上面程序的含义没有安静的变化(即 X::a 在两种情况下都试图访问,这在上面的例子中给出了访问错误)。如果 public/protected/private 控制可见性,程序的含义就会改变(全局 a 将用 private 调用,否则 X::a)。

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

这与您的示例有什么关系?主要是因为标准使重载决策符合名称查找先于访问控制的一般规则。

10.2 Member name lookup [class.member.lookup]

1 Member name lookup determines the meaning of a name (id-expression) in a class scope (3.3.7). Name lookup can result in an ambiguity, in which case the program is ill-formed. For an id-expression, name lookup begins in the class scope of this; for a qualified-id, name lookup begins in the scope of the nestedname- specifier. Name lookup takes place before access control (3.4, Clause 11).

8 If the name of an overloaded function is unambiguously found, overloading resolution (13.3) also takes place before access control. Ambiguities can often be resolved by qualifying a name with its class name.