在下面的代码中,使用std::for_each
表达式对boost:bind
进行了两次“等效”调用。指示的行编译,指示的失败行失败。我能在标准中找到的最佳解释相当于“因为我们这么说”。我正在寻找“为什么标准表明这种行为”。我的假设在下面。
我的问题很简单:为什么指示的行编译和等效的后续行无法编译(我不想因为“标准这样说”,我已经知道 - 我不会接受任何给出的答案这是一个解释;我想解释为什么标准所说的那样。)
注意:虽然我使用boost,但boost与此问题无关,并且使用g ++ 4.1。*和VC7.1重现了各种格式的错误。
#include <boost/bind.hpp>
#include <iostream>
#include <map>
#include <algorithm>
class Base
{
protected:
void foo(int i)
{ std::cout << "Base: " << i << std::endl; }
};
struct Derived : public Base
{
Derived()
{
data[0] = 5;
data[1] = 6;
data[2] = 7;
}
void test()
{
// Compiles
std::for_each(data.begin(), data.end(),
boost::bind(&Derived::foo, this,
boost::bind(&std::map<int, int>::value_type::second, _1)));
// Fails to compile - why?
std::for_each(data.begin(), data.end(),
boost::bind(&Base::foo, this,
boost::bind(&std::map<int, int>::value_type::second, _1)));
}
std::map<int, int> data;
};
int main(int, const char**)
{
Derived().test();
return 0;
}
指示的行因此错误而失败: main.C:在成员函数'void Derived :: test()'中: main.C:9:错误:'void Base :: foo(int)'受保护 main.C:31:错误:在此上下文中
如上所述,上面所谓的等效语句干净地编译(如果有问题的陈述被注释掉,则在预定的结果中运行“5”,“6”,“7”在不同的行上)。
在寻找解释时,我在标准中遇到了11.5.1(具体来说,我正在查看2006-11-06草案):
超出额外的访问权限检查 那些在第11节中描述的那些 在非静态数据时应用 成员或非静态成员的职能是 其命名类的受保护成员 (11.2)105)如前所述, 访问受保护的成员是 因为引用发生而被授予 在朋友或某些C班的成员。 如果访问是形成指针 成员(5.3.1),. nested-name-specifier应命名为C或 一个派生自C的类。所有其他 访问涉及(可能是隐含的) 对象表达式(5.2.5)。在这 case,对象的类 表达式应为C或类 源自C。
在读完这篇文章之后,第一个成功的第二个陈述失败的原因显而易见,但后来问题出现了:这个理由是什么?
我最初的想法是编译器正在扩展boost :: bind模板,发现Base :: foo受到保护并将其踢掉,因为boost :: bind&lt; ...&gt;不是朋友。但是,我越是想到这个解释,它就越少有意义,因为如果我没记错的话,只要你把指针指向一个成员(假设你最初是在成员的访问控制范围内),所有的访问控制信息都是丢失(即我可以定义一个函数,该函数返回一个指向成员的任意指针,该成员根据某些输入交替返回公共成员,受保护成员或私有成员,并且返回者不会更聪明。)
我想到了更多,并且我能想出的唯一合理的解释是什么它应该有所作为是多重继承的情况。具体来说,根据类布局,从Base计算的成员指针与从Derived计算的成员指针不同。
答案 0 :(得分:7)
这完全是关于“背景”的。在第一次调用中,调用的上下文是Derived
,它可以访问Base
的受保护成员,因此可以获取它们的地址。在第二个上下文是“Derived
之外,因此在Base
之外,因此不允许受保护的成员访问。
答案 1 :(得分:1)
实际上,这似乎是合乎逻辑的。继承使您可以访问Derived :: foo而不是Base :: foo。让我用代码示例来说明:
struct Derived : public Base
{
void callPrivateMethod(Base &b)
{
// this should obviously fail
b.foo(5);
// pointer-to-member call should also fail
void (Base::*pBaseFoo) (int) = &Base::foo; // the same error as yours here
(b.*pBaseFoo)(5);
}
};
答案 2 :(得分:0)
此限制的原因是跨共享基础的不同类强制实施访问控制。
Core Language Defects Report defect #385中的注释加强了这一点,相关部分在此处复制以供参考:
[...]我们有这个规则的原因是
C
使用继承的受保护成员可能与它们在兄弟类中的使用不同,比如说D
。因此,C
的成员和朋友只能以B::p
的使用方式使用C
,即C
或衍生自 - {{ 1}}对象。
作为此规则阻止的内容的示例:
C
我们希望编译器强制执行class B {
protected:
void p() { };
};
class C : public B {
public:
typedef void (B::*fn_t)();
fn_t get_p() {
return &B::p; // compilation error here, B::p is protected
}
};
class D : public B { };
int main() {
C c;
C::fn_t pbp = c.get_p();
B * pb = new D();
(pb->*pbp)();
}
的受保护状态,但如果上面编译的情况不是这样的话。