2015-07-20 10 views
17

Ho un problema riguardante le dichiarazioni di accesso in g ++ (versione 5.1).La dichiarazione di accesso pubblico non influisce sui puntatori di funzioni dei membri?

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 ++ non riesce a compilare il codice precedente con:

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)();

Anche quando la funzione stessa può essere chiamato (d.doStuff();) il puntatore non può essere utilizzata anche se ho dichiarato la funzione accessibile da l'esterno. L'ereditarietà privata è anche importante, in una certa misura, perché la classe Derived sceglie di esporre solo un determinato insieme di membri dalla/e base/e che sono implementazioni dell'interfaccia IRL.

NB: questa è una domanda sulla lingua, non sul design della classe.

+0

compile con clang ++: D –

+0

@GabrieldeGrimouard Il programma è sbagliato, clang dovrebbe (e lo fa) rifiutarlo anche. – Barry

+0

@Barry era uno scherzo :(e nessun clang ++ con -std = C++ 11 lo compilo sul mio computer –

risposta

18

Il problema è che &Derived::doStuff non è in realtà un puntatore a un membro della classe Derived. Da [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 non è un membro di Derived. È un membro di Base. Quindi ha tipo puntatore al membro di Base o void (Base::*)(). Quello che il dichiarazione using fa qui è semplicemente un aiuto per sovraccaricare la risoluzione, da [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.

Ecco perché d.doStuff() opere. Tuttavia, tramite il puntatore della funzione, stai tentando di chiamare una funzione membro Base su un oggetto Derived. Non c'è alcuna risoluzione di sovraccarico qui poiché si utilizza direttamente un puntatore di funzione, quindi la funzione di classe base sarebbe inaccessibile.

Si potrebbe pensare che si potrebbe semplicemente lanciare &Derived::doStuff del tipo "corretto":

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

Ma non si può fare neppure secondo [conv.mem], dal momento che ancora una volta Base è una base inaccessibile 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

Che ne dici di un cast-cast? Si fonderà? – Quentin

+0

C'è una soluzione? Il membro può essere chiamato attraverso un puntatore a membro? – JorenHeit

+0

@Quentin: 'rofl .png' –

3

Credo che il motivo è che la funzione di membro non è realmente parte della classe derivata, ma piuttosto della classe base. Questo può essere dimostrato in qualche modo empiricamente ispezionando il tipo del puntatore a funzione membro e confrontandolo con un puntatore alla funzione di base:

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

Live here.

Attualmente sto cercando lo standard per alcuni retroscena su questo. La risposta di Barry contiene le rispettive parti dello standard.

1

Secondo lo 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.

Quindi, si sta solo introducendo Base::doStuff nella regione Derived, è ancora una funzione di membro di Base.

Poi exec viene istanziato come exec<Derived, void (Base::*)()>, ma non può lanciare un Derived*-Base* a causa della ereditarietà privata.

1

Dalla 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 
}; 

Annotare il B :: g è un sinonimo pubblico per A :: g parte. Quando prendi l'indirizzo di Derived::doStuff, GCC sta creando un puntatore alla funzione membro di tipo void(Base::*)() e lo standard dice che sta andando bene. Quindi, penso che l'errore di compilazione sia giusto.

+1

Gli esempi non sono normativi. – Barry

Problemi correlati