在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: In实例化‘void exec(C*,Func) with C= Derived;Func =void(Base::*)()’:test.cpp:24:27:此处需要
函数:错误:‘base’是‘test.cpp:17:4’(c->*func)()的一个不可访问的基;
即使函数本身可以被调用(d.doStuff();
),也不能使用指针,即使我声明函数是可以从外部访问的。私有继承在某种程度上也很重要,因为Derived
类选择只公开基类中的特定成员集,基类是接口实现IRL。
注意:这是一个关于语言的问题,不是类设计的问题。
发布于 2015-07-20 22:11:12
问题是&Derived::doStuff
实际上并不是指向类Derived
的成员的指针。来自expr.unary.op
一元
&
运算符的结果是指向其操作数的指针。操作数应为左值或限定id。如果操作数是一个限定id,用于命名类型为T
的某个类C
的非静态或可变成员m
,则结果的类型为“指向类型为T
的类C
的成员的指针”,并且是一个指定为C::m
的prvalue。
doStuff
不是Derived
的成员。它是Base
的成员。因此,它具有指向Base
或void (Base::*)()
成员的类型指针。using-声明在这里所做的仅仅是帮助来自namespace.udecl的重载解析
出于重载解析的目的,通过使用声明引入派生类的函数将被视为派生类的成员。
这就是d.doStuff()
工作的原因。但是,通过函数指针,您正在尝试调用Derived
对象上的Base
成员函数。这里没有重载解析,因为你直接使用了一个函数指针,所以基类函数将是不可访问的。
您可能认为只需将&Derived::doStuff
转换为“正确”类型:
exec(&d, static_cast<void (Derived::*)()>(&Derived::doStuff));
但根据conv.mem,您也不能这样做,因为Base
是Derived
的一个不可访问的基础
“指向类型为cv
T
的B
的成员的指针”类型的PRV值,其中B
是类类型,可以被转换为“指向类型为cvT
的D
的成员的指针”类型的PR值,其中D
是B
的派生类(第10条)。如果B
是不可访问(第11条)的、歧义(10.2)或D
的虚拟(10.1)基类,或者是D
的虚拟基类的基类,则需要进行此转换的程序是格式错误的。
发布于 2015-07-20 22:22:27
根据标准namespace.udecl
使用声明将一个名称引入到声明区域中,在该区域中将出现使用声明。
如果使用声明命名了一个构造函数(3.4.3.1),它会隐式地在出现该使用声明的类中声明一组构造函数(12.9);否则,在使用声明中指定的名称是另一个名称空间或类中的一组声明的同义词。
所以您只是将Base::doStuff
引入Derived
区域,它仍然是Base
的成员函数。
然后exec
被实例化为exec<Derived, void (Base::*)()>
,但由于私有继承,它不能将Derived*
转换为Base*
。
发布于 2015-07-20 22:23:53
来自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::*)()
类型的成员函数的指针,而标准说它做得很好。因此,我认为编译时错误是公平的。
https://stackoverflow.com/questions/31518214
复制相似问题