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

时间:2016-08-19 15:02:16

标签: c++ overloading overload-resolution member-functions

考虑以下代码:

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的私有时不调用public const方法?

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

11 个答案:

答案 0 :(得分:126)

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

void foo() const

void foo()

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

答案 1 :(得分:34)

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

  

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

以及同一部分第1条中的注意

  

[注意:过载分辨率选择的功能不保证适合上下文。其他限制(例如函数的可访问性)可能使其在调用上下文中的使用不正确。 - 结束说明]

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

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

答案 2 :(得分:30)

假设在重载解析之前访问控制。实际上,这意味着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提到当前规则(可访问性之前的可见性)的好处是(暂时)将private内的class X转换为public(例如,为了调试的目的)是上述程序的含义没有安静的变化(即在两种情况下都试图访问X::a,这在上例中给出了访问错误)。如果public/protected/private会控制可见性,则程序的含义会发生变化(全局a将使用private进行调用,否则将X::a调用。

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

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

  

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

     

1会员名称查找确定名称的含义(id-expression)   在一个范围内(3.3.7)。名称查找可能导致歧义   在哪种情况下,该计划是不正确的。对于id-expression,名称   查询从此类的范围开始;对于限定ID,名称   查询从nestedname-specifier的范围开始。 名称查找   在访问控制之前进行(3.4,第11条)。

     

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

答案 3 :(得分:23)

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

如果明确标记非const一个private,则解析将失败,编译器将无法继续搜索。

答案 4 :(得分:20)

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

  1. 找到所有可行的功能。
  2. 选择最佳的可行功能。
  3. 如果没有一个最佳可行,或者如果您实际上无法调用最佳可行功能(由于访问冲突或函数为delete d),则失败。
  4. (3)在(2)之后发生。这非常重要,因为否则使函数delete d或private变得毫无意义且难以推理。

    在这种情况下:

    1. 可行的功能是A::foo()A::foo() const
    2. 最佳可行功能是A::foo(),因为后者涉及隐式this参数的资格转换。
    3. 但是A::foo()private并且您无法访问它,因此代码格式不正确。

答案 5 :(得分:14)

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

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

  1. 它搜索以找到第一个 1 范围,其中某事具有该名称。

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

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

  4. 最后,编译器会检查所选功能是否可访问。

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

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

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

答案 6 :(得分:12)

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

答案 7 :(得分:11)

在此电话会议中:

a.foo();

每个成员函数中始终存在隐式this指针。 const的{​​{1}}限定条件取自调用引用/对象。编译器将上述调用视为

this

但你有A::foo(a); 的两个声明,其被视为

A::foo

通过重载分辨率,第一个将被选为非const A::foo(A* ); A::foo(A const* ); ,第二个将被选为this。如果您删除第一个,则第二个将绑定到const thisconst non-const

在重载解析后选择最佳可行功能,来访问控制。由于您将所选重载的访问权限指定为this,因此编译器将会抱怨。

标准如此说:

  

[class.access/4] ...对于重载的函数名称,将应用访问控制   超载分辨率选择的功能....

但如果你这样做:

private

然后,只有A a; const A& ac = a; ac.foo(); 重载才适合。

答案 8 :(得分:9)

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

  

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

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

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

答案 9 :(得分:8)

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

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

答案 10 :(得分:8)

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

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