2015-07-20 8 views
17

Ich habe ein Problem bezüglich der Zugriffserklärungen unter g ++ (Version 5.1).Public Access-Deklaration wirkt sich nicht auf Mitgliedsfunktionszeiger aus?

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 ++ nicht mit dem obigen Code zu kompilieren:

test.cpp: In instantiation of ‘void exec(C*, Func) [with C = Derived; Func = void (Base::*)()]’: test.cpp:24:27: required from here
test.cpp:17:4: error: ‘Base’ is an inaccessible base of ‘Derived’ (c->*func)();

Auch wenn die Funktion selbst aufgerufen werden kann (d.doStuff();) der Zeiger kann nicht verwendet werden, selbst wenn ich die Funktion als zugänglich deklarierte die Außenseite. Private Vererbung ist auch wichtig, bis zu einem gewissen Grad, weil die Derived Klasse beschließt, nur eine bestimmte Gruppe von Mitgliedern aus Basis (en) zu offenbaren, die Schnittstellenimplementierungen IRL sind.

NB: Dies ist eine Frage über die Sprache, nicht Klasse Design.

+0

kompilieren mit clang ++: D –

+0

@GabrieldeGrimouard Das Programm ist falsch, clang sollte es auch (und tut es) ablehnen. – Barry

+0

@Barry es war ein Witz :(und keine Clang ++ mit -std = C++ 11 kompilieren es auf meinem Computer –

Antwort

18

Das Problem ist, dass &Derived::doStuff ist eigentlich kein Zeiger auf ein Mitglied der Klasse Derived. Von [expr.unary.op]:

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static or variant member m of some class C with type T , the result has type “pointer to member of class C of type T ” and is a prvalue designating C::m .

doStuff ist kein Mitglied von Derived. Es ist ein Mitglied von Base. Daher hat es Zeiger auf Element von Base oder void (Base::*)(). Was die mit Deklaration hier tut, ist einfach eine Hilfe Auflösung zu überlasten, von [namespace.udecl]:

For the purpose of overload resolution, the functions which are introduced by a using-declaration into a derived class will be treated as though they were members of the derived class.

, deshalb, d.doStuff() funktioniert. Mit dem Funktionszeiger versuchen Sie jedoch, eine Base Elementfunktion auf einem Objekt Derived aufzurufen. Es gibt hier keine Überladungsauflösung, da Sie einen Funktionszeiger direkt verwenden, sodass die Basisklassenfunktion nicht verfügbar ist.

Man könnte denken, Sie könnten werfen nur &Derived::doStuff auf die „richtige“ Art:

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

Aber man kann das nicht tun, entweder nach [conv.mem], da wieder Base ist eine unzugängliche Basis Derived:

A prvalue of type “pointer to member of B of type cvT ”, where B is a class type, can be converted to a prvalue of type “pointer to member of D of type cvT ”, where D is a derived class (Clause 10) of B . If B is an inaccessible (Clause 11), ambiguous (10.2), or virtual (10.1) base class of D , or a base class of a virtual base class of D , a program that necessitates this conversion is ill-formed.

+3

Was ist mit einem C-Cast? Wird es sich vermischen? – Quentin

+0

Gibt es eine Lösung? Kann das Member überhaupt über einen Memberfunktionszeiger aufgerufen werden? – JorenHeit

+0

@Quentin: 'rofl .png' –

3

ich glaube, der Grund dafür ist, dass der Mitgliedsfunktionsteil der abgeleiteten Klasse nicht wirklich, sondern vielmehr von der Basisklasse. Dies kann irgendwie empirisch gezeigt werden, indem die Art des Elements Funktionszeiger Inspektion und es mit einem Zeiger auf dem Basiselement Funktion Vergleich:

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

Live here.

Ich bin derzeit den Standard für einige Hintergrundinformationen suchen darauf. Barrys Antwort enthält die entsprechenden Teile des Standards.

1

Nach dem stardard [namespace.udecl]:

A using-declaration introduces a name into the declarative region in which the using-declaration appears.

If a using-declaration names a constructor (3.4.3.1), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9); otherwise the name specified in a using-declaration is a synonym for a set of declarations in another namespace or class.

Sie sind also nur Base::doStuff in die Derived Region einzuführen, es ist immer noch eine Member-Funktion von Base.

Dann wird exec als exec<Derived, void (Base::*)()> instanziiert, aber es kann keine Derived*-Base* wegen der privaten Erbschaft geworfen.

1

Aus dem C++ 11 Standard, §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 
}; 

Notiere die B :: g ist ein öffentliches Synonym für A :: g Teil. Wenn Sie die Adresse Derived::doStuff nehmen, erstellt GCC einen Zeiger auf Elementfunktion des Typs void(Base::*)(), und der Standard sagt, dass es gut geht. Also, ich denke, der Kompilierzeitfehler ist fair.

+1

Beispiele sind nicht normativ. – Barry

Verwandte Themen