公共访问声明不影响成员函数指针?

时间:2015-07-20 13:47:58

标签: c++ inheritance language-lawyer

我在g ++(版本5.1)下有关于访问声明的问题。

class Base
{
public:
    void doStuff() {}
};

class Derived : private Base
{
public:
    // Using older access declaration (without using) shoots a warning
    // and results in the same compilation error
    using Base::doStuff;
};

template<class C, typename Func>
void exec(C *c, Func func)
{
    (c->*func)();
}

int main()
{
    Derived d;
    // Until here, everything compiles fine
    d.doStuff();
    // For some reason, I can't access the function pointer
    exec(&d,&Derived::doStuff);
}

g ++无法使用以下代码编译上述代码:

  

test.cpp:实例化'void exec(C *,Func)[用C = Derived; Func = void(Base :: *)()]':   test.cpp:24:27:从这里要求
  test.cpp:17:4:错误:'Base'是'Derived'无法访问的基础     (C-&GT * FUNC)();

即使可以调用函数本身(d.doStuff();),即使我将函数声明为可从外部访问,也无法使用指针。 在某种程度上,私有继承也很重要,因为Derived类选择仅从接口实现IRL的基础中公开某组成员。

注意:这是关于语言的问题,而不是课堂设计。

4 个答案:

答案 0 :(得分:19)

问题是&Derived::doStuff实际上并不是指向类Derived成员的指针。来自[expr.unary.op]:

  

一元&运算符的结果是指向其操作数的指针。操作数应为左值或 qualified-id 。   如果操作数是 qualified-id ,则命名类型为m的某个类C的非静态或变体成员T,   结果的类型为“指向类C的类T成员的指针”,并且是指定C::m的prvalue。

doStuff不是Derived的成员。它是Base的成员。因此,它具有指向Basevoid (Base::*)()成员的类型指针。 using-declaration 在这里所做的只是帮助重载解析,来自[namespace.udecl]:

  

出于重载解析的目的,由 using-declaration 引入的函数   派生类将被视为派生类的成员。

这就是d.doStuff()有效的原因。但是,通过函数指针,您尝试在Base对象上调用Derived成员函数。由于您直接使用函数指针,因此没有重载解析,因此基类函数将无法访问。

您可能认为可以将&Derived::doStuff转换为“正确”类型:

exec(&d, static_cast<void (Derived::*)()>(&Derived::doStuff));

但根据[conv.mem],你不能这样做,因为Base再次成为Derived无法访问的基础:

  

“指向{em> cv B <{1}}成员的类型的prvalue”,其中T是类类型,可以转换为   “指向{em> cv B <{1}}成员的类型的prvalue”,其中DT的派生类(第10条) 。如果D是   不可访问(第11条),模糊(10.2)或虚拟(10.1)基类B,或虚基的基类   B的类,一个需要这种转换的程序是不正确的。

答案 1 :(得分:3)

我想原因是成员函数实际上不是派生类的一部分,而是基类的一部分。这可以通过检查成员函数指针的类型并将其与指向基本成员函数的指针进行比较来以经验方式显示:

cout << typeid(&Derived::doStuff).name() << endl
  << typeid(& Base::doStuff).name() << endl;

Live here.

我目前正在搜索标准中的一些背景信息。 巴里的答案包含了标准的各个部分。

答案 2 :(得分:1)

根据标准[namespace.udecl]:

  

using声明在声明区域中引入了一个名称,其中出现了using声明。

     

如果using声明为构造函数(3.4.3.1)命名,则隐式   在类中声明一组构造函数   使用声明出现(12.9);否则指定的名称   using-declaration是另一个声明集的同义词   命名空间或类。

因此,您只是将Derived引入Base区域,它仍然是exec的成员函数。

然后exec<Derived, void (Base::*)()>被实例化为Derived*,但由于私有继承,它无法将Base*强制转换为{{1}}。

答案 3 :(得分:1)

从C ++ 11标准,§7.3.3[namespace.udecl],18:

class A
{
private:
    void f( char );
public:
    void f( int );
protected:
    void g();
};
class B : public A
{
    using A::f; // error: A::f(char) is inaccessible
public:
    using A::g;
    // B::g is a public synonym for A::g
};

注意 B :: g是A :: g 部分的公共同义词。当你取Derived::doStuff的地址时,GCC正在创建一个指向void(Base::*)()类型成员函数的指针,标准说它表现良好。所以,我认为编译时错误是公平的。