it-swarm.com.de

zugriff auf ein geschütztes Mitglied einer Basisklasse in einer anderen Unterklasse

Warum kompiliert das:

class FooBase
{
protected:
    void fooBase(void);
};

class Foo : public FooBase
{
public:
    void foo(Foo& fooBar)
    {
        fooBar.fooBase();
    }
};

aber das geht nicht?

class FooBase
{
protected:
    void fooBase(void);
};

class Foo : public FooBase
{
public:
    void foo(FooBase& fooBar)
    {
        fooBar.fooBase();
    }
};

Auf der einen Seite gewährt C++ allen Instanzen dieser Klasse Zugriff auf private/geschützte Member, aber auf der anderen Seite gewährt es keinen Zugriff auf geschützte Member einer Basisklasse für alle Instanzen einer Unterklasse mir.

Ich habe das Kompilieren mit VC++ und mit ideone.com getestet und beide kompilieren das erste, aber nicht das zweite Code-Snippet.

34
Kaiserludi

Wenn foo eine FooBase-Referenz erhält, weiß der Compiler nicht, ob das Argument ein Nachkomme von Foo ist. Es muss also davon ausgegangen werden, dass dies nicht der Fall ist. Foo hat Zugriff auf vererbte geschützte Member von anderen Foo-Objekten , nicht auf alle anderen Geschwisterklassen.

Betrachten Sie diesen Code:

class FooSibling: public FooBase { };

FooSibling sib;
Foo f;
f.foo(sib); // calls sib.fooBase()!?

Wenn Foo::foo geschützte Mitglieder beliebiger FooBase-Nachkommen aufrufen kann, kann sie die geschützte Methode von FooSibling aufrufen, die keine direkte Beziehung zu Foo hat. So soll der geschützte Zugang nicht funktionieren.

Wenn Foo Zugriff auf geschützte Mitglieder aller FooBase-Objekte benötigt, nicht nur auf diejenigen, von denen bekannt ist, dass sie Foo-Nachkommen sind, muss Foo ein Freund von FooBase sein:

class FooBase
{
protected:
  void fooBase(void);
  friend class Foo;
};
30
Rob Kennedy

Das C++ FAQ fasst dieses Problem gut zusammen:

[Sie] dürfen Ihre eigenen Taschen auswählen, aber Sie dürfen weder die Taschen Ihres Vaters noch die Taschen Ihres Bruders auswählen.

20
h0b0

Der Schlüsselpunkt ist, dass protected Ihnen Zugriff auf Ihre eigene Kopie des Mitglieds gewährt, nicht auf die Mitglieder in any other object. Dies ist ein weitverbreitetes Missverständnis, da wir häufig verdeutlichen und angeben, dass protected dem Member Zugriff auf den abgeleiteten Typ gewährt (ohne dass dies explizit nur für die eigenen Basen angegeben wird.

Nun, dies ist aus einem bestimmten Grund, und im Allgemeinen sollten Sie nicht auf das Element in einem anderen Zweig der Hierarchie zugreifen, da Sie die Invarianten zerstören könnten, von denen andere Objekte abhängen. Stellen Sie sich einen Typ vor, der eine kostspielige Berechnung für einige große Datenelemente (geschützt) durchführt, und zwei abgeleitete Typen, die das Ergebnis nach verschiedenen Strategien zwischenspeichern:

class base {
protected:
   LargeData data;
// ...
public:
   virtual int result() const;      // expensive calculation
   virtual void modify();           // modifies data
};
class cache_on_read : base {
private:
   mutable bool cached;
   mutable int cache_value;
// ...
   virtual int result() const {
       if (cached) return cache_value;
       cache_value = base::result();
       cached = true;
   }
   virtual void modify() {
       cached = false;
       base::modify();
   }
};
class cache_on_write : base {
   int result_value;
   virtual int result() const {
      return result_value;
   }
   virtual void modify() {
      base::modify();
      result_value = base::result(); 
   }
};

Der cache_on_read-Typ erfasst Änderungen an den Daten und markiert das Ergebnis als ungültig, sodass der nächste read des Werts neu berechnet wird. Dies ist ein guter Ansatz, wenn die Anzahl der Schreibvorgänge relativ hoch ist, da wir die Berechnung nur bei Bedarf durchführen (d. H., Mehrfache Modifizierungen lösen keine Neuberechnungen aus). Der cache_on_write berechnet das Ergebnis im Voraus, was eine gute Strategie sein kann, wenn die Anzahl der Schreibvorgänge gering ist und Sie deterministische Kosten für das Lesen benötigen (denken Sie an eine niedrige Latenzzeit beim Lesen).

Nun zurück zum ursprünglichen Problem. Beide Cachestrategien behalten eine strengere Anzahl von Invarianten als die Basis bei. Im ersten Fall ist die zusätzliche Invariante, dass cached nur true ist, wenn data nach dem letzten Lesevorgang nicht geändert wurde. Im zweiten Fall ist die zusätzliche Invariante, dass result_value jederzeit der Wert der Operation ist.

Wenn ein dritter abgeleiteter Typ einen Verweis auf eine base nahm und auf data zum Schreiben zugegriffen hat (wenn protected dies zulässt), würde er mit den Invarianten der abgeleiteten Typen brechen.

Davon abgesehen ist die Spezifikation der Sprache broken (persönliche Meinung), da sie eine Hintertür verlässt, um dieses bestimmte Ergebnis zu erzielen. Wenn Sie einen Zeiger auf das Mitglied eines Mitglieds von einer Basis in einem abgeleiteten Typ erstellen, wird der Zugriff in derived geprüft, der zurückgegebene Zeiger ist jedoch ein Zeiger auf das Element von base, das auf any base angewendet werden kann. Objekt:

class base {
protected:
   int x;
};
struct derived : base {
   static void modify( base& b ) {
      // b.x = 5;                        // error!
      b.*(&derived::x) = 5;              // allowed ?!?!?!
   }
}

In beiden Beispielen erbt Foo eine geschützte Methode fooBase. In Ihrem ersten Beispiel versuchen Sie jedoch, auf die angegebene geschützte Methode von derselben Klasse (Foo::foo-Aufrufe Foo::fooBase) zuzugreifen, während Sie im zweiten Beispiel versuchen, auf eine geschützte Methode von einer anderen Klasse zuzugreifen, die nicht als Freundklasse (Foo::foo try) deklariert ist um FooBase::fooBase aufzurufen, der fehlschlägt, ist der spätere geschützt).

3
Zeta

Im ersten Beispiel übergeben Sie ein Objekt vom Typ Foo, das die Methode fooBase () offensichtlich erbt und daher aufrufen kann. Im zweiten Beispiel versuchen Sie einfach, eine geschützte Funktion aufzurufen, unabhängig davon, in welchem ​​Kontext Sie eine geschützte Funktion nicht von einer Klasseninstanz aus aufrufen können, in der sie deklariert ist .. Im ersten Beispiel erben Sie die geschützte Methode fooBase und so haben Sie das Recht, es IN Foo-Kontext zu nennen

1
Moataz Elmasry

Ich neige dazu, Dinge in Begriffen und Botschaften zu sehen. Wenn Ihre FooBase-Methode eigentlich "SendMessage" genannt wurde und Foo "EnglishSpeakingPerson" und FooBase war SpeakingPerson, soll Ihre protected - Deklaration SendMessage auf zwischen EnglishSpeakingPersons (und Unterklassen) beschränken, z. Ein anderer von SpeakingPerson abgeleiteter Typ FrenchSpeakingPerson könnte keine SendMessage empfangen, es sei denn, Sie haben FrenchSpeakingPerson als Freund deklariert, wobei "Freund" bedeutet, dass FrenchSpeakingPerson eine besondere Fähigkeit hat, SendMessage von EnglishSpeakingPerson zu empfangen (dh Englisch verstehen).

1
Sentinel

Zusätzlich zu hobos Antwort Sie können einen Workaround suchen.

Wenn die Unterklassen die fooBase-Methode aufrufen möchten, können Sie sie als static festlegen. Auf statische geschützte Methoden kann durch Unterklassen mit allen Argumenten zugegriffen werden.

0
yairchu