122

考虑这段代码:

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()' 是私有的。

但是当我删除私人的时候它就可以工作了。当非常量方法是私有的时,为什么不调用公共 const 方法?

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

4

11 回答 11

126

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

void foo() const

void foo()

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

请记住,在重载决议中,它不是“找到最佳可用函数”。它是“找到最好的功能并尝试使用它”。如果由于访问限制或被删除而无法访问,则会出现编译器错误。

换句话说,为什么重载解决方案出现在访问控制之前?

好吧,让我们看看:

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

于 2016-08-19T15:18:01.793 回答
37

归根结底,这归结为标准中的断言,即在执行重载解析时不应考虑可访问性。这个断言可以在[over.match]第 3 条中找到:

...当重载决议成功,并且最佳可行函数在使用它的上下文中不可访问(子句 [class.access])时,程序是错误的。

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

[注意:重载决议选择的函数不能保证适合上下文。其他限制,例如函数的可访问性,可能使其在调用上下文中的使用格式错误。——尾注]

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

  1. 它可以防止由于更改重载候选者的可访问性而导致意外的行为更改(相反,会发生编译错误)。
  2. 它从重载决策过程中消除了上下文相关性(即,无论在类内部还是外部,重载决策都会产生相同的结果)。
于 2016-08-19T15:39:12.570 回答
33

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

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

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

这与您的示例有什么关系?基本上是因为标准使重载解析符合名称查找在访问控制之前的一般规则。

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

1 成员名称查找确定类范围(3.3.7)中名称(id-expression)的含义。名称查找可能会导致歧义,在这种情况下程序格式错误。对于 id 表达式,名称查找从 this 的类范围开始;对于qualified-id,名称查找在nestedname-specifier 的范围内开始。名称查找发生在访问控制之前(3.4,第 11 条)。

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

于 2016-08-19T19:36:48.950 回答
23

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

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

于 2016-08-19T15:05:48.900 回答
20

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

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

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

在这种情况下:

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

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

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

  1. 它搜索以找到具有该名称的内容的第一个1范围。

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

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

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

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

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


  1. “第一”本身可能有点复杂,特别是当/如果涉及模板时,因为它们可能导致两阶段查找,这意味着在进行搜索时有两个完全独立的“根”开始。基本的想法很简单:从最小的封闭范围开始,然后向外工作到越来越大的封闭范围。
于 2016-08-19T15:20:22.500 回答
12

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

于 2016-08-19T15:20:18.457 回答
11

在本次通话中:

a.foo();

每个成员函数中总是有一个隐式this指针可用。并且const资格this来自调用引用/对象。编译器将上述调用视为

A::foo(a);

但是你有两个声明A::foo视为

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

通过重载决议,第一个将被选择为 non-const this,第二个将被选择为 a const this。如果删除第一个,则第二个将绑定到constnon-const this

在重载决议以选择最佳可行功能之后,访问控制。由于您指定对所选重载的访问权限为private,因此编译器会抱怨。

标准是这样说的:

[class.access/4] : ...在重载函数名的情况下,访问控制应用于重载决议选择的函数......

但如果你这样做:

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

然后,只const适合过载。

于 2016-08-19T15:12:17.583 回答
9

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

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

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

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

于 2016-08-19T15:35:45.947 回答
8

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

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

于 2016-08-19T15:24:00.390 回答
8

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

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

于 2016-08-19T15:05:05.843 回答