【问题标题】:boost::bind with protected members & contextboost::bind 与受保护的成员和上下文
【发布时间】:2011-03-04 17:18:52
【问题描述】:

在下面的代码中,有两个使用boost:bind 表达式对std::for_each 的“等效”调用。指示的行编译,指示的失败行失败。我能在标准中找到的最佳解释是“因为我们这么说”。我正在寻找“为什么标准表明这种行为”。我的猜测如下。

我的问题很简单:为什么指定的行编译而等效的下一行无法编译(我不希望因为“标准这么说”,我已经知道 - 我不会接受任何给出的答案这是一个解释;我想解释一下为什么标准这么说)。

注意:虽然我用的是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), 嵌套名称说明符应命名为 C 或 从 C 派生的类。所有其他 访问涉及(可能是隐式的) 对象表达式(5.2.5)。在这个 案例,对象的类 表达式应为 C 或类 源自 C。

读完后,很明显为什么第二个语句失败而第一个语句成功了,但是问题来了:这是什么原理?

我最初的想法是编译器正在扩展 boost::bind 模板,发现 Base::foo 受到保护并将其踢出,因为 boost::bind<...> 不是朋友。但是,我对这个解释想得越多,它就越没有意义,因为如果我没记错的话,一旦你把指针指向一个成员(假设你最初是在成员的访问控制范围内),所有的访问控制信息都是丢失(即我可以定义一个函数,该函数返回一个指向成员的任意指针,该成员根据某些输入交替返回一个公共、受保护或私有成员,而返回者不会更聪明)。

我想了很多,我能想出的唯一合理的解释为什么它应该有所作为是在多重继承的情况下。具体来说,根据类布局,从 Base 计算的成员指针与从 Derived 计算的成员指针不同。

【问题讨论】:

  • 我会立即注意到:这可能需要一个更好的主题。如果有人能给我一个更好的,我会改变它。提前谢谢。
  • 因为标准是这么说的。
  • 看起来Base::foo 应该有virtual。加进去有效果吗?
  • @zildjohn01 我没有覆盖 Base::foo,所以没有。它纯粹是试图访问基础成员。另请注意,不涉及模板,因此也不是依赖名称问题。
  • 看起来有一个更简单的案例在相同的错误上失败。不需要 boost 绑定,您需要做的就是在 test() 中写入 &amp;Base::Foo; 并且它会失败。我也很困惑为什么会这样。

标签: c++


【解决方案1】:

这都是关于“上下文”的。在第一次调用中,调用的上下文是Derived,它可以访问Base 的受保护成员,因此可以获取它们的地址。在第二个中,上下文在“Derived 之外”,因此在Base 之外,因此不允许受保护的成员访问。

【讨论】:

    【解决方案2】:

    实际上,这似乎是合乎逻辑的。继承使您可以访问 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);
        }
    };
    

    【讨论】:

      【解决方案3】:

      这种限制的原因是在共享一个共同基础的不同类之间实施访问控制。

      Core Language Defects Report defect #385 中的注释加强了这一点,相关部分复制在这里以供参考:

      [...] 我们有这个规则的原因是C 对继承的受保护成员的使用可能与它们在同级类中的使用不同,比如D。因此C 的成员和朋友只能以与C 的用法一致的方式使用B::p,即在C 或派生自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)();
      }
      

      D::p 的受保护状态是我们希望编译器强制执行的,但如果上面编译的情况并非如此。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-05
        • 2012-09-27
        • 2013-01-08
        • 2016-06-12
        • 2014-06-09
        • 2014-05-13
        • 1970-01-01
        相关资源
        最近更新 更多