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

Nar*_*rek 114 c++ overloading member-functions overload-resolution

考虑以下代码:

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();
}
Run Code Online (Sandbox Code Playgroud)

编译器错误是:

错误:'void A :: foo()'是私有的.

但是当我删除私有它时它才起作用.为什么在非const的私有时不调用public const方法?

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

Nat*_*ica 126

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

void foo() const
Run Code Online (Sandbox Code Playgroud)

void foo()
Run Code Online (Sandbox Code Playgroud)

现在,既然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);
}
Run Code Online (Sandbox Code Playgroud)

现在让我们说我实际上并不打算void foo(Derived * d)私有化.如果访问控制首先出现,则该程序将编译并运行Base并将被打印.这可能很难在大型代码库中进行跟踪.由于访问控制是在重载解析后出现的,我得到了一个很好的编译器错误,告诉我我想要它调用的函数无法调用,我可以更容易地找到错误.

  • @ drake7707当我在我的代码示例中显示你是否首先访问控制时,上面的代码会编译,这会改变程序的语义.不确定你,但我宁愿有一个错误,需要做一个显式的演员,如果我希望函数保持私有然后隐式演员和代码默默"工作". (3认同)

atk*_*ins 35

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

...当重载决策成功,并且在使用它的上下文中无法访问最佳可行功能(Clause [class.access])时,程序就会形成错误.

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

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

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

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


Tem*_*Rex 32

假设访问控制在重载解决之前出现.实际上,这意味着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?
};
Run Code Online (Sandbox Code Playgroud)

斯特劳斯提到,当前规则的好处(能见度可达性之前)是(暂时)chaning的private内部class X进入public(例如用于调试的目的)是有在上述程序的含义没有安静的变化(即X::a是试图在两种情况下都可以访问,这在上面的例子中给出了访问错误).如果public/protected/private控制可见性,程序的含义将会改变(全局a将被调用private,否则X::a).

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

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

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

1成员名称查找确定类范围(3.3.7)中名称(id-expression)的含义.名称查找可能导致歧义,在这种情况下程序是不正确的.对于id-expression,名称查找从此类的范围开始; 对于qualified-id,名称查找从nestedname-specifier的范围开始.名称查找在访问控制之前进行(3.4,第11条).

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


Bat*_*eba 23

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

如果明确标记非constone,private则分辨率将失败,编译器将不会继续搜索.

  • @Narek我相信它的工作原理就像删除的函数在重载决策中的作用一样.它从集合中选择最好的一个,然后它看到它不可用,因此您得到编译器错误.它没有选择最好的可用功能,而是最好的功能然后尝试使用它. (5认同)
  • @Narek我也首先想知道为什么它不起作用,但考虑一下:如果公共const函数也应该被选择用于非const对象,你将如何调用私有函数? (3认同)

Bar*_*rry 20

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

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

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

在这种情况下:

  1. 可行的功能是A::foo()A::foo() const.
  2. 最可行的功能是A::foo()因为后者涉及隐式this参数的资格转换.
  3. 不过A::foo()private和你没有访问它,因此是形成不良的代码.


Jer*_*fin 14

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

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

  1. 它搜索找到的第1个范围,在其中有一些使用该名称.

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

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

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

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

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


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


Pet*_*ker 12

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


Whi*_*TiM 11

在这个电话中:

a.foo();
Run Code Online (Sandbox Code Playgroud)

this每个成员函数中始终都有一个隐式指针.并且const资格this来自调用引用/对象.上述调用由编译器处理为:

A::foo(a);
Run Code Online (Sandbox Code Playgroud)

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

A::foo(A* );
A::foo(A const* );
Run Code Online (Sandbox Code Playgroud)

通过重载分辨率,第一个将被选择为非const this,第二个将被选择为a const this.如果你删除第一个,第二个将绑定到constnon-const this.

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

标准如此说:

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

但是如果你这样做:

A a;
const A& ac = a;
ac.foo();
Run Code Online (Sandbox Code Playgroud)

然后,只有const过载才适合.


son*_*yao 9

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

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

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

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


Som*_*ude 8

因为变量amain函数未声明的const.

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


Kyl*_*and 8

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

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