non-const Pointer auf private



  • Na so ungefähr (auswendig getippt) zum Beispiel:

    class A
    {
    private:
    char a[80];

    public:
    char* adr(){ return a; }
    };

    Dann kann man etwa mit strcpy(a.adr(), ":)"); den Private-Datenbereich verändern.

    Es wäre schon schön, wenn solche Dinge schon nicht in der Sprachdefinition
    ausgeschlossen werden, daß zumindest der Compiler warnt, denn damit wird die OOP
    in gewissem Sinne ad absurdum geführt.



  • ... wobei im Beispiel natürlich a eine Instanz der Klasse A ist.



  • const char* adr() const { return a; }



  • Wenn deine Argumentation so zöge, dann wären zum Beispiel std::vector und std::string unmöglich - immerhin gibt da z.B. der operator[] eine Referenz auf eine Variable zurück, die im privaten Speicherbereich des vectors/strings liegt.

    Noch offensichtlicher wird es vielleicht bei Bäumen:

    template<typename val_t>
    class tree {
    public:
      typedef val_t value_type;
    
      tree(value_type const &x) : value_(x), left_(0), right_(0) { }
      ~tree() {
        delete left_;
        delete right_;
      }
    
      value_type const &value() const { return value_; }
      void insert(value_type const &x) {
        if(x > value_) {
          if(right_ == 0) right_ = new tree(x);
          else right_->insert(x);
        } else {
          if(left_ == 0) left_ = new tree(x);
          else left_->insert(x);
        }
      }
    
      // ermöglicht Traversion des Baumes
      tree       *left ()       { return left_ ; }
      tree const *left () const { return left_ ; }
      tree       *right()       { return right_; }
      tree const *right() const { return right_; }
    
    private:
      tree *left_, *right_;
      int value_;
    };
    

    ...da dürfte es kaum anders möglich sein. Zumindest fällt mir grad kein intelligenter Weg dazu ein.



  • Eben - in einem wasserdichten OO-Modell sollte es aber doch nicht
    möglich sein, dem Benutzer einen Zeiger auf Private-Daten auszuliefern, mit dem
    dieser dann Private-Daten manipulieren kann.

    Für C++ würde das heißen: public-Methoden, die Zeiger oder Referenzen auf private-Daten zurückgeben, sollten nur const-Zeiger zurückgeben dürfen.

    Das Problem wird dann wirklich relevant, wenn andere Programmierer, die die
    Klasse verwenden, sich an die spezielle Struktur der Private-Daten gewöhnen,
    auf die sie mittels Zeiger Zugriff haben - dann ist eine Änderung der Organisation
    von Private-Daten möglicherweise kaum mehr machbar, ohne daß alle Benutzer
    der Klasse ihre Programme umschreiben müssen; der Vorteil der OOP wäre damit
    weg.



  • Cacheline schrieb:

    Eben - in einem wasserdichten OO-Modell sollte es aber doch nicht
    möglich sein, dem Benutzer einen Zeiger auf Private-Daten auszuliefern, mit dem
    dieser dann Private-Daten manipulieren kann.

    Ne, da hast du was falsch verstanden. Wenn der Programmierer einen Zeiger zurückgibt, mit dem man jede Menge Unsinn anstellen kann, dann ist es seine eigene Schuld, nicht die des OOP Konzepts. Vielmehr liegt hier die Problematik beim C++ Typkonzepts. Und gerade mit OOP kann man dieses noch sicherer machen, indem man dann zB op[] oder op* überlädt. Das Ziel sollte ja sein, eine Klasse so wasserdicht zu machen, das nichts passieren kann. Und wenn das jemand nicht schafft, dann sollte er zuerst die Fehler in seiner Implementierung suchen, und nicht bei der Sprache.



  • Cacheline schrieb:

    Eben - in einem wasserdichten OO-Modell sollte es aber doch nicht möglich sein, dem Benutzer einen Zeiger auf Private-Daten auszuliefern, mit dem dieser dann Private-Daten manipulieren kann.

    Dieses Modell wäre nicht nur wasserdicht, sondern auch "benutzerdicht". Container müssten z. Bsp. außer einer einheitlichen Schinttstelle für den Zugriff auf ihre Elemente, deren Verwaltung public machen, was doch bestimmt viele Clients verwirren würde.
    //edit
    Das sollte man vielleicht noch kurz erklären:
    Angenommen man hat eine verkettete Liste. Dort werden die Elemente in Knoten verwaltet, die miteinander verkettet sind. Wenn man diese Knoten nicht public machen würde, dürfte man diesem OO-Modell zufolge keinen abstrahierten Schreibzugriff auf die Elemente veröffentlichen, da dieser die privaten Knoten verändert.

    Cacheline schrieb:

    Für C++ würde das heißen: public-Methoden, die Zeiger oder Referenzen auf private-Daten zurückgeben, sollten nur const-Zeiger zurückgeben dürfen.

    Die meisten anderen Sprachen sind stärker von dem Problem betroffen, auch wenn meistens keine Zeiger unterstützt werden (erlaubt sind), werden bei denen in der Regel weder const-Methoden, noch const-Referenzen vom Compiler unterstützt (oder nicht erlaubt?). Wenn man dort Zugriff auf ein privates Datum gewähren will, gibt es im Normalfall nur zwei Möglichkeiten: entweder kopieren oder eine Referenz zurückgeben über die das private Datum fleißig manipuliert werden kann. In C++ hingegen kann man eine Referenz zurückgeben, über die das private Datum nicht manipuliert werden darf. Führe dir folgendes Beispiel zu Gemüte:

    #include <iostream>
    
    using namespace std;
    
    class Bar
    {
    public:
    	Bar() : m_i(5) { }
    	void ManipulateMember()	{ m_i = 7; }
    	void PrintMember() const { cout << m_i << endl;	}
    
    private:
    	int m_i;
    };
    
    class Foo
    {
    public:
    	Foo(Bar bar) : m_bar(bar) { }
    	const Bar &getBar() const { return m_bar; }
    
    private:
    	Bar m_bar;
    };
    
    int main()
    {
    	Foo f = Foo( Bar() );
    	Bar b = f.getBar(); // Es wird eine Kopie erzeugt
    	//Bar &bb = f.getBar(); //Vom Compiler verboten!
    	const Bar &bb = f.getBar();
    
    	b.ManipulateMember();
    	b.PrintMember();
    
    	//bb.ManipulateMember(); //Vom Compiler verboten!
    	bb.PrintMember();//ok
    
    	//f.getBar().ManipulateMember(); //Vom Compiler verboten!
    	f.getBar().PrintMember(); //ok
    
    	cin.get();    
    }
    


  • Cache: es zwingt dich niemand dazu, Zeiger auf interne Datenstrukturen zurückzuliefern. Wenn du nicht willst, dass sie geändert werden und dir die Möglichkeit vorbehalten willst, die internen Strukturen noch anders zu gestalten, dann musst du sowieso nicht nur const-referenzen sonder kopien der Werte zurückliefern. Die Methoden, die nichtkonstante Zeiger zurückliefern sind nicht falsch, weil man drauf zugreifen kann, sondern sie sind für den schreibenden Zugriff gedacht, sonst wären sie const. Wenn du also einen nichtkonstanten Zeiger zurückgibst, obwohl du keinen Zugriff erlauben willst, ist das dein eigenes Verschulden und nicht das des Compilers, schließlich kann der nicht wissen was du vorhast udn was nicht.
    Klar widerspricht das ein wenig den klassischen Regeln der OOP, andererseits gibts nicht selten Wrapperklassen, die eine Schnittstelle für ein Objekt anbieden, auf das aber genausogut direkt zugegriffen werden kann. Man kann die Übergabe eines Zeigers auf datenmember also so sehen, dass dieses Datenmember nicht private ist sondern ein öffentliches Objekt, für das die eigentliche Klasse lediglich ein bequemes Interface bietet, das aber genausgut direkt angesprochen werden kann. (Wie z.B. der char[], den die C++-strings verwalten, auf den man mittels string::data() aber auch direkt zugreifen kann...)



  • Ich meine ja nur, daß der Compiler eine Warnung abgeben könnte bei einem
    public non-const Pointer auf private-Daten.
    Andererseits: ich weiß nicht, irgendwie verursacht das bei mir einen komischen
    Nachgeschmack, das man dieses Grundprinzip der OOP (Datenkapselung) durch Übergabe
    eines Zeigers so "mir nichts dir nichts" unterlaufen kann.



  • wie schon oben gesagt, man kann es als implementierung einer wrapperklasse mit gleichezitig möglichem zugriff auf die dahinterliegende klasse interpretieren. Man hat ja auch die Möglichkeit, membervariablen public zu deklarieren, und AFAIk spuckt der compiler auch da keine Warnung aus. Und wie schon oben gesagt, ist es Sache des Programmierers, wie exakt er sich an die Prinzipien der OOP hält und wo er etwas freier damit umgeht. Weder C++ noch die Compiler machen da irgendwelche Einschränkungen, sie bieten nur die Möglichkeiten (aber nicht die Pflicht), das Prinzip der Kapselung etc. umzusetzen.
    Btw: wenn mans ganz genau nimmt, sind auch die Deklarationen privater Membervariablen und Methoden eine Verletzung des Kapselungsprinzips, weils implemetationsdetails sind, für die sich der Klient nicht zu interessieren hat. Andererseits würde eine Umsetzung der ganz strengen OOP-richtlinien in C++ die Zahl der Klassen mindestens verdoppeln, wenns überhaupt möglich ist.
    Ein Beispiel sähe etwa so aus:
    header:

    class MyClass
      {
      class MyClassImpl;
      MyClassImpl * impl; //ist privat, sagt aber nichts aus
      public:
      //CTors, DTor, Operatoren-, Methoden- DEKLARATIONEN
      void mymethod();  //bsp.
      };
    

    .cpp:

    MyClass::MyClass(parameter) : impl(new MyClassImpl(parameter)) {} //so sehen die CTors aus
    MyClass::~MyClass() { delete impl; } //so der DTor
    
    void MyClass::mymethod() { impl->mymethod(); } //so werden alle Methoden etc. implementiert
    
    //Jetzt kommt die eigentliche Implemetnierungsklasse, in der die Daten und alles stecken:
    class MyClass::MyClassImpl
      {
      //private member
      //private methoden
      public:
      //alle Methoden, die MyClass auch hat (und weiterreicht)
      void mymethod();
      }
    
    //Eigentliche Implementierungen der Methoden
    
    void MyClass::MyClassImpl::mymethod() { so_something; }
    

    So kann man die Kapselung streng aufrechterhalten, aber zu was für einem Preis...


Anmelden zum Antworten