从派生类成员函数中std::bind()一个基类受保护成员函数

14

我想要在派生类中将bind()绑定到基类版本的函数。该函数在基类中标记为受保护的。当我这样做时,代码可以在Clang(Apple LLVM Compiler 4.1)中编译通过,但在g++ 4.7.2和Visual Studio 2010中会出现错误。错误信息大致为:“'Base::foo' : cannot access protected member.”

这意味着引用的上下文实际上是在bind()中,在那里当然会将该函数视为受保护的。但是bind()不应该继承调用函数的上下文吗?在这种情况下,即Derived::foo(),它应该能够访问基类方法才对吧?

以下程序说明了这个问题。

struct Base
{
protected: virtual void foo() {}
};

struct Derived : public Base
{
protected:
    virtual void foo() override
    {
        Base::foo();                        // Legal

        auto fn = std::bind( &Derived::foo, 
            std::placeholders::_1 );        // Legal but unwanted.
        fn( this );

        auto fn2 = std::bind( &Base::foo, 
            std::placeholders::_1 );        // ILLEGAL in G++ 4.7.2 and VS2010.
        fn2( this );
    }
};

为什么行为会出现差异?哪一个是正确的?对于给出错误的编译器有什么解决办法?


Derived::foo 调用自身是有意为之,还是仅仅是简化示例的结果? - aschepler
@aschepler 这就是“合法但不受欢迎”的“不受欢迎”部分。 - OldPeculier
2个回答

11
答案:参见boost::bind with protected members & context,其中引用了标准的这部分内容。

当一个非静态数据成员或非静态成员函数是其命名类(11.2)的受保护成员时,除了在第11条款中描述的访问检查之外,还应用了其他访问检查。如前所述,对受保护成员的访问被授予是因为引用出现在一些类C的朋友或成员中。如果访问是要形成指向成员的指针(5.3.1),则嵌套名称限定符应命名C或从C派生的类。所有其他访问涉及对象表达式(5.2.5)(可能是隐式的)。在这种情况下,对象表达式的类应该是C或从C派生的类。

解决方法:将foo变为public成员函数。

#include <functional>

struct Base
{
public: virtual void foo() {}
};

3
如果你不能改变 Base,可以通过派生方法来适应它(例如 void basefoo(){ Base::foo(); })。令人惊讶和令人困惑的是,在 Visual C++(Express 2010)中,实际上可以使用 &Derived::Base::foo 来避开这个错误,但它基本上忽略了你的第二个参数(即它总是使用 this)。 - Apprentice Queue
1
解决方法的替代方案,使用lambda表达式: auto fn = [=](Derived* d) { d->foo(); }; fn(this); - James Mart

10

这与 bind 没有关系。由于 @rhalbersma 引用的标准部分,表达式 &Base::foo 在非友元成员函数 Derived 中是非法的,在任何情况下。

但是如果您想要做类似于调用 Base::foo(); 的操作,那么您将面临更大的问题:成员函数指针总是会调用虚拟重写。

#include <iostream>

class B {
public:
    virtual void f() { std::cout << "B::f" << std::endl; }
};

class D : public B {
public:
    virtual void f() { std::cout << "D::f" << std::endl; }
};

int main() {
    D d;
    d.B::f();   // Prints B::f

    void (B::*ptr)() = &B::f;
    (d.*ptr)(); // Prints D::f!
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接