2012-08-13 22 views
19

uwzględniając klasę tak:Dlaczego nie można użyć metody prywatnej w lambda?

class A { 
public: 
    bool hasGrandChild() const; 

private: 
    bool hasChild() const; 
    vector<A> children_; 
}; 

Dlaczego nie jest możliwe użycie prywatną metodę hasChild() w wyrażeniu lambda określonym w metodzie hasGrandChild() jak to?

bool A::hasGrandChild() const { 
    return any_of(children_.begin(), children_.end(), [](A const &a) { 
     return a.hasChild(); 
    }); 
} 

Compiler wystawia błąd, że metoda hasChild() jest prywatny w kontekście. Czy istnieje jakieś obejście?

Edytuj: Wygląda na to, że kod, który napisałem, działa oryginalnie. Myślałem, że jest to równoważne, ale kod, który does not work on GCC więcej tak:

#include <vector> 
#include <algorithm> 

class Foo; 

class BaseA { 
protected: 
    bool hasChild() const { return !children_.empty(); } 
    std::vector<Foo> children_; 
}; 

class BaseB { 
protected: 
    bool hasChild() const { return false; } 
}; 

class Foo : public BaseA, public BaseB { 
public: 
    bool hasGrandChild() const { 
    return std::any_of(children_.begin(), children_.end(), [](Foo const &foo) { 
     return foo.BaseA::hasChild(); 
     }); 
    } 
}; 

int main() 
{ 
    Foo foo; 
    foo.hasGrandChild(); 
    return 0; 
} 

Wydaje się, że istnieje problem z pełni kwalifikowanych nazw jako this does not work, ale this works.

+1

Rodzaj zamknięcia nie ma związku z klasy 'A', tak naturalnie, że nie ma dostępu do' członków niepublicznych A' użytkownika. Nie może też nigdy, ponieważ nazwa jego rodzaju jest niepoznawalna, więc nie można jej nawet nazwać "przyjacielem". –

+2

Czy to tylko ja lub działa to na gcc? http://ideone.com/333qw – pmr

+0

@pmr: Tak, wydaje się działać w starszych wersjach GCC, ale nie działa w nowszych. –

Odpowiedz

27

To wydaje się być po prostu bug GCC w szczególnym przypadku, gdy lambda próbuje uzyskać dostęp do chronionego członka z klasy nadrzędnej, używając pełnej nazwy. This does not work:

class Base { 
protected: 
    bool hasChild() const { return !childs_.empty(); } 
    std::vector<Foo> childs_; 
}; 

class Foo : public Base { 
public: 
    bool hasGrandChild() const { 
    return std::any_of(childs_.begin(), childs_.end(), [](Foo const &foo) { 
     return foo.Base::hasChild(); 
    }); 
    } 
}; 

, ale this works:

class Foo : public Base { 
public: 
    bool hasGrandChild() const { 
    return std::any_of(childs_.begin(), childs_.end(), [](Foo const &foo) { 
     return foo.hasChild(); 
    }); 
    } 
}; 

Według C++ 11, 5.1.2/3:

Typ lambda ekspresji (który jest również typ obiektu zamknięcia ) jest unikalnym, nienazwanym typem klasy niepowiązanym - zwanym typem zamknięcia - którego właściwości opisano poniżej. Ten typ klasy nie jest agregatem (8.5.1). Typ zamknięcia jest zadeklarowany w najmniejszym zasięgu bloku, zasięgu klasy lub zakresu przestrzeni nazw, który zawiera odpowiadające wyrażenie lambda.

A, a następnie C++ 11, 11,7/1:

zagnieżdżony jest członkiem klasy i jako takich samych praw dostępu co każdy inny użytkownik.

Wspomniana funkcja lambda dla funkcji powinna mieć takie same prawa dostępu, jak każdy inny członek klasy. Dlatego powinna być w stanie wywołać metodę chronioną z klasy nadrzędnej.

+1

Świetne wyjaśnienie, dlaczego to * powinno * działać. (I dlaczego większość innych odpowiedzi tutaj jest błędnych.) +1 – etherice

0

Nie jest to możliwe, ponieważ lambda nie jest częścią klasy. Jest to to samo, co tworzenie funkcji poza klasą i wywoływanie jej zamiast tworzenia lambda. Oczywiście nie ma dostępu do prywatnych członków.

9

Standard (C++ 11 §5.1.2/3) wskazuje, że

Typ lambda ekspresji (który jest również rodzaj obiektu zamknięcia) jest unikalnym, nienazwany typ klasy niezłącznej - o nazwie z zamknięciem typu.

Ponieważ jest to unikalny rodzaj klasy, która nie jest friend z A, nie ma dostępu do prywatnych członków A „s.

Co tutaj robi kompilator, utwórz typ klasy, który ma odpowiednich członków do przechwytywania przechwyconych zmiennych, odpowiedniego operator() itd. - to jest dokładnie to, co sam napiszesz, jeśli chcesz emulować lambdy w C++ 03. Ten typ z pewnością nie miałby dostępu do członków private, co może ułatwić wizualizację, dlaczego ograniczenie istnieje i dlaczego nie istnieje obejście .

Aktualizacja dotyczące możliwych obejścia:

byłoby lepiej powiedzieć „nie ma obejścia za pomocą lambda”, ponieważ w ogóle nie istnieją obejścia chociaż wymagają one, że rezygnują z wygodnego składni lambda. Na przykład, można:

  1. Napisz lokalny typ klasy jawnie rejestruje this wraz z innymi mieszkańcami to wymaga (zainspirowany komentarzem Björn POLLEX jest poniżej).
  2. Napisz metodę private zamiast lambda i przekazuj ją jako wywołanie zwrotne (na przykład dla wygody przy użyciu std::bind). Jeśli chcesz uchwycić mieszkańców oprócz this możesz użyć więcej std::bind na stronie połączenia, aby to zrobić.
+2

Działa jednak z lokalną 'struct' (http://ideone.com/AvsBE). Czy możesz wyjaśnić różnicę? –

+0

@ BjörnPollex: To jest * lokalna * 'struct', dlatego ma dostęp do prywatnych członków jej typu zawierającego (cytując książkę: * Klasa lokalna jest objęta zasięgiem obejmującym i ma ten sam dostęp do nazw spoza funkcji, podobnie jak funkcja otaczania. *). Można powiedzieć, że jest to funkcjonalne obejście. – Jon

+0

@Jon: To nie wyjaśnia, dlaczego kompilator nie sprawia, że ​​typ zamknięcia jest przyjacielem klasy, której jest członkiem.Jeśli może przechwycić "to" pośrednio, a więc skutecznie działając jak funkcja członkowska, powinno być w stanie dotrzeć do innych prywatnych członków. –

3

Obejście:

typedef bool (A::*MemFn)(void) const; 

bool A::hasGrandChild() const { 
    MemFn f = &A::hasChild; 
    return any_of(childs_.begin(), childs_.end(), [=](A const &a) { 
      return (a.*f)(); 
    }); 
} 
+1

Czekaj. Dlaczego do cholery nie zrobiłbyś po prostu 'any_of (begin, end, std :: mem_fn (& A :: hasChild)) niż? Poza drobną karą za wyniki. – pmr

+0

@pmr - Ma to głównie na celu zilustrowanie sposobu uzyskiwania dostępu do prywatnych funkcji członków w wyrażeniach lambda. – Henrik

+0

@pmr: Kara umowna? Nie oczekiwałbym jednego; dla rozsądnego kompilatora dość oczywiste jest, że 'f' nie zmienia się. – MSalters

3

Można przechwycić jawnie this i uczynić go "członkiem lambda", który ma dostęp do prywatnych członków.

Na przykład, należy rozważyć następujące próbki:

#include <iostream> 
class A { 
private: 
    void f() { std::cout << "Private"; } 
public: 
    void g() { 
     [this] { 
      f(); 
      // doesn't need qualification 
     }(); 
    } 
}; 
class B { 
private: 
    void f() { std::cout << "Private"; } 
public: 
    void g() { [] { f(); }(); } // compiler error 
}; 
int main() { 
    A a; 
    a.g(); 
} 
Powiązane problemy