Pytanie Deklaracja dostępu publicznego nie wpływa na wskaźniki funkcji członków?


Mam problem dotyczący deklaracji dostępu w g ++ (wersja 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 ++ nie potrafi skompilować powyższego kodu z:

test.cpp: W tworzeniu wystąpienia 'void exec (C *, Func) [z C = Derived; Func = void (Base :: *) ()] ':   test.cpp: 24: 27: wymagane tutaj
  test.cpp: 17: 4: error: "Baza" jest niedostępną bazą "Pochodnię"     (c -> * func) ();

Nawet jeśli można wywołać samą funkcję (d.doStuff();) wskaźnika nie można użyć, mimo że zadeklarowałem funkcję jako dostępną z zewnątrz. Dziedziczenie prywatne jest również ważne, w pewnym stopniu, ponieważ Derived class wybiera wystawienie tylko określonego zestawu członków z bazy (baz), które są implementacjami interfejsu IRL.

Uwaga: to jest pytanie dotyczące języka, a nie klasy.


18
2017-07-20 13:47


pochodzenie


skompiluj z klangiem ++: D - Gabriel de Grimouard
@GabrieldeGrimouard Program jest nieprawidłowy, clang powinien (i nie) odrzucić go. - Barry
@ Barry to był żart :( i bez clangu ++ z -std = c ++ 11 skompiluj go na moim komputerze - Gabriel de Grimouard
@ Barar Więc mam pytanie. Dlaczego kompiluje się z wersją klang 3.0-6.2? - Gabriel de Grimouard
@GabrieldeGrimouard To błąd. na przykład 19274 - Barry


Odpowiedzi:


Problemem jest &Derived::doStuff w rzeczywistości nie jest wskaźnikiem dla członka klasy Derived. Od [expr.unary.op]:

Rezultat unary & operator jest wskaźnikiem do swojego operandu. Argument będzie l-wartości lub kwalifikowany identyfikator.   Jeśli argumentem jest a kwalifikowany identyfikator nazywanie elementu niestatycznego lub wariantowego m jakiejś klasy C z typem T,   wynik ma typ "wskaźnik do członka klasy C typu T"I jest wyznacznikiem prvalue C::m.

doStuff nie jest członkiem Derived. Jest członkiem Base. Stąd ma wskaźnik typu na członka Base, lub void (Base::*)(). Co deklaracja użycia tutaj jest po prostu pomoc do przeciążenia rozdzielczości z [namespace.udecl]:

W celu uzyskania rozdzielczości przeciążeniowej, funkcje wprowadzane przez a deklaracja użycia do a   klasa pochodna będzie traktowana tak, jakby była członkami klasy pochodnej.

Dlatego d.doStuff() Prace. Jednak za pomocą wskaźnika funkcji próbujesz wywołać a Base funkcja członka na Derived obiekt. Nie ma tu żadnej rozdzielczości przeciążania, ponieważ używasz bezpośrednio wskaźnika funkcji, więc funkcja klasy bazowej byłaby niedostępna.

Możesz pomyśleć, że możesz po prostu rzucić &Derived::doStuff do "poprawnego" typu:

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

Ale nie można tego zrobić według [conv.mem], ponieważ znowu Base jest niedostępną podstawą Derived:

Wartość typu "wskaźnik do członka B typu cv  T", gdzie B jest typem klasy, można przekonwertować na   prvalue typu "wskaźnik do członka D typu cv  T", gdzie D jest klasą pochodną (klauzula 10) B. Gdyby B to jest    niedostępne (klauzula 11), niejednoznaczna (10.2) lub wirtualna (10.1) klasa podstawowa Dlub podstawowa klasa wirtualnej bazy   klasa D, program, który wymaga tej konwersji, jest źle sformułowany.


19
2017-07-20 14:11



A co z odlewem C? Czy to się zmiksuje ? - Quentin
Czy istnieje rozwiązanie? Czy członek może zostać wywołany w ogóle przez wskaźnik funkcji członka? - JorenHeit
@Quentin: rofl​.png - Lightness Races in Orbit
@Barry: Robisz to naprawdę dobrze język-prawniking. Dobra robota. - Lightness Races in Orbit
@JorenHeit Nope. Nie ze wskaźnikami do funkcji członka. Zawsze jest std::function<void()> f = [d]{ d->doStuff(); }; - Barry


Sądzę, że powodem jest to, że funkcja member nie jest częścią klasy pochodnej, a raczej klasy bazowej. Można to wykazać empirycznie, sprawdzając typ wskaźnika funkcji elementu i porównując go ze wskaźnikiem do funkcji podstawowego elementu:

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

Żyć tutaj.

W tej chwili szukam standardu dla jakiegoś tła. Odpowiedź Barry'ego zawiera odpowiednie części normy.


3
2017-07-20 14:13





Zgodnie ze standardem [namespace.udecl]:

Deklaracja użycia wprowadza nazwę do regionu deklaratywnego, w którym pojawia się deklaracja użycia.

Jeśli deklaracja użycia nazywa konstruktor (3.4.3.1), jest to pośrednio   deklaruje zestaw konstruktorów w klasie, w której   pojawia się deklaracja użycia (12,9); w przeciwnym razie nazwa określona w a   using-declaration jest synonimem zestawu deklaracji w innym   przestrzeń nazw lub klasa.

Więc dopiero się wprowadzasz Base::doStuff do Derived region, nadal jest funkcją członka Base.

Następnie exec jest tworzone jako exec<Derived, void (Base::*)()>, ale nie może obsłużyć Derived* do Base* z powodu prywatnego dziedziczenia.


1
2017-07-20 14:22





Ze standardu 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
};

Zanotuj B :: g jest publicznym synonimem A :: g część. Kiedy weźmiesz adres Derived::doStuffGCC tworzy wskaźnik do funkcji składowej typu void(Base::*)(), a standard mówi, że ma się dobrze. Myślę więc, że błąd czasu kompilacji jest sprawiedliwy.


1
2017-07-20 14:23



Przykłady nie są normatywne. - Barry