2010-06-16 13 views
5

Im folgenden Code gibt es zwei "äquivalente" Aufrufe an std::for_each mit boost:bind Ausdrücken. Die angegebene Zeile kompiliert, die angegebene fehlgeschlagene Zeile schlägt fehl. Die beste Erklärung, die ich im Standard finden kann, ist "weil wir das gesagt haben". Ich suche nach "warum der Standard dieses Verhalten anzeigt". Meine Vermutungen sind unten.boost :: bind mit geschützten Elementen und Kontext

Meine Frage ist einfach: Warum kompiliert die angegebene Zeile und die äquivalente folgende Zeile nicht kompilieren (und ich will nicht, weil "der Standard sagt so", ich weiß bereits, dass - ich werde keine Antworten akzeptieren, dass geben Sie dies als eine Erklärung, ich möchte eine Erklärung zu warum der Standard sagt so).

Anmerkungen: Obwohl ich Boost verwende, ist Boost für diese Frage irrelevant, und der Fehler in verschiedenen Formaten wurde mit g ++ 4.1. * Und VC7.1 reproduziert.

#include <boost/bind.hpp> 
#include <iostream> 
#include <map> 
#include <algorithm> 

class Base 
{ 
protected: 
     void foo(int i) 
     { std::cout << "Base: " << i << std::endl; } 
}; 

struct Derived : public Base 
{ 
     Derived() 
     { 
       data[0] = 5; 
       data[1] = 6; 
       data[2] = 7; 
     } 

     void test() 
     { 
       // Compiles 
       std::for_each(data.begin(), data.end(), 
         boost::bind(&Derived::foo, this, 
           boost::bind(&std::map<int, int>::value_type::second, _1))); 

       // Fails to compile - why? 
       std::for_each(data.begin(), data.end(), 
         boost::bind(&Base::foo, this, 
           boost::bind(&std::map<int, int>::value_type::second, _1))); 
     } 

     std::map<int, int> data; 
}; 

int main(int, const char**) 
{ 
     Derived().test(); 

     return 0; 
} 

Die angegebene Zeile nicht mit diesem Fehler: MAIN.C: In Memberfunktion 'Leerer Derived :: test()': MAIN.C: 9: Fehler: ‚Leere Basis :: foo (int) 'ist geschützt main.C: 31: Fehler: in diesem Zusammenhang

Wie bereits erwähnt, kompiliert die angeblich äquivalente Aussage sauber (und wenn die beleidigende Aussage auskommentiert ist, läuft mit dem erwarteten Ergebnis des Druckens "5" , "6", "7" in getrennten Zeilen).

Während Suche nach einer Erklärung, ich auf 11.5.1 im Standard kam (genauer gesagt, ich bin am 2006-11-06 Entwurf suchen):

An additional access check beyond those described earlier in clause 11 is applied when a non-static data member or nonstatic member function is a protected member of its naming class (11.2)105) As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1), the nested-name-specifier shall name C or a class derived from C. All other accesses involve a (possibly implicit) object expression (5.2.5). In this case, the class of the object expression shall be C or a class derived from C.

Nach der Lektüre dieses, wurde es offenbar warum die zweite Aussage gescheitert ist, während die erste erfolgreich war, aber dann kam die Frage auf: Was ist der Grund dafür?

Mein erster Gedanke war, dass der Compiler die boost :: bind Templates erweiterte und entdeckte, dass Base :: foo geschützt war und es rausschmiss, weil boost :: bind < ...> kein Freund war. Aber je mehr ich über diese Erklärung nachdachte, desto weniger ergab es Sinn, denn wenn ich mich richtig erinnere, sobald Sie den Zeiger auf ein Mitglied nehmen (vorausgesetzt, Sie befinden sich zunächst in der Zugriffskontrolle des Mitglieds), sind alle Zugriffskontrollinformationen verloren (dh ich könnte eine Funktion definieren, die einen willkürlichen Zeiger auf ein Element zurückgibt, das abwechselnd ein öffentliches, geschütztes oder privates Element zurückgibt, abhängig von irgendeiner Eingabe, und der Rückgeber wäre keiner der weisere).

Mehr Ich habe darüber nachgedacht, und die einzige plausible Erklärung, die ich finden konnte, warum es einen Unterschied machen sollte, war im Falle der Mehrfachvererbung. Insbesondere würde sich der Mitgliedszeiger, wenn er von Base berechnet wird, in Abhängigkeit vom Klassenlayout von dem von Derived berechneten unterscheiden.

+0

Ich werde sofort bemerken: Dies ist wahrscheinlich ein besseres Thema benötigt. Wenn mir jemand einen besseren geben kann, werde ich es ändern. Danke im Voraus. –

+1

Weil der Standard das sagt. –

+1

Es sieht so aus, als würde 'Base :: foo'' virtual' haben. Funktioniert es, wenn Sie das hinzufügen? – zildjohn01

Antwort

5

Es geht um "Kontext". In dem ersten Anruf ist der Kontext des Anrufs Derived, der Zugriff auf die geschützten Mitglieder von Base hat und daher Adressen von ihnen nehmen darf. In der zweiten ist der Kontext "außerhalb von" Derived und daher außerhalb von Base, so dass der Zugriff auf geschützte Elemente nicht erlaubt ist.

1

Eigentlich scheint dies logisch. Die Vererbung gibt Ihnen Zugriff auf Derived :: foo und nicht auf Base :: foo. Lassen Sie mich das anhand eines Codebeispiels veranschaulichen:

0

Der Grund für diese Einschränkung ist die Durchsetzung der Zugriffssteuerung über verschiedene Klassen hinweg, die eine gemeinsame Basis haben.

Dies wird durch Noten in Core Language Defects Report defect #385 verstärkt, der relevanten Teil hier als Referenz kopiert:

[...] the reason we have this rule is that C 's use of inherited protected members might be different from their use in a sibling class, say D . Thus members and friends of C can only use B::p in a manner consistent with C 's usage, i.e., in C or derived-from- C objects.

Als ein Beispiel für etwas, diese Regel verhindert:

class B { 
protected: 
    void p() { }; 
}; 

class C : public B { 
public: 
    typedef void (B::*fn_t)(); 
    fn_t get_p() { 
     return &B::p; // compilation error here, B::p is protected 
    } 
}; 

class D : public B { }; 

int main() { 
    C c; 
    C::fn_t pbp = c.get_p(); 
    B * pb = new D(); 
    (pb->*pbp)(); 
} 

Der Schutzstatus von D::p ist etwas, Wir wollen den Compiler erzwingen, aber wenn das oben kompiliert würde, wäre das nicht der Fall.

Verwandte Themen