Hängende Zeiger vermeiden



  • @chris4cpp sagte in Hängende Zeiger vermeiden:

    Kann man denn wirklich auf Zeiger verzichten? Ich meine bei meiner Audio Lib muss ich schon allein wegen der callback Funktion auch mit Zeigern arbeiten. Gut geschenkt, das ist nur eine WrapperKlasse die ich dafür geschrieben haben.

    Mit „auf Zeiger verzichten“ meint in diesem Kontext, dass man keine rohen Zeiger mehr nutzt. Es gibt in modernen C++ Mechanismen die das einem ermöglichen. Z.B. bei den SmartPointer wird intern, für Nutzer der Klasse komplett verdeckt, natürlich ein Zeiger genutzt. Entscheidend ist nun, dass man mit diesen modernen Methoden massenweise Programmierfehler eliminieren kann, die so typisch für altes C++ und insbesondere C sind. Deshalb ist so wichtig, dass man weiß wie man es „richtig“ macht, und möglichst den Gebrauch von rohen Zeiger vermeidet.

    Ich gehöre zu den wenigen hier im Forum, die beständig schreiben, dass man in C++ realistischer Weise nicht auf rohe Zeiger auf absehbare Zeit verzichten kann, weil es noch immer APIs gibt, die das erfordern. Wie Du selbst schreibst, bist Du genau auf eine solche API gestoßen. Deshalb bleibt das Thema rohe Zeiger für C++ Neulinge ein Thema. Aber meiner Meinung nach sollte man nicht damit anfangen, sondern zuerst C++ lernen und dann als Fortgeschrittenen Thema rohe Zeiger behandeln. Nicht weil man das Problem mit einer geeigneten Wrapperklasse nicht weg abstrahieren könnte, sondern weil einem Anfänger das Wissen fehlt wie man es richtig macht. Wichtig hierbei Rule of Three bzw. Rule of Five durchlesen und dann vollständig umsetzen.



  • Und es gibt noch den Bereich, wenn man selbst Datenstrukturen mit verketteten Listen oder ähnlichem baut. Wenn man nämlich (einfach) verkettete Listen mit einem unique_ptr baut und die Liste groß wird, ist die rekursive Aufräumstrategie nämlich nicht so eine tolle Idee... Gut, wie oft ist das relevant?

    Außerdem gibt es einige Frameworks, die selbst intern tracken, welche Objekte erzeugt wurden. Wenn man dann ein Eltern-Objekt deleted, deleten die gleich die Kinder (oder was sie für ihre Kinder halten) mit. Bei GUIs ist das ja noch einleuchtend, aber das passiert auch teilweise anderswo. Noch besser, wenn es dann irgendwo versteckte Switches gibt, um das Verhalten an- und auszuschalten. Da kann man sich mit Smartpointern auch in den Fuß schießen. Aber ansonsten: wenn möglich, std::unique_ptr nutzen!



  • @wob
    std::list?

    aber ich habe doch noch einen fall gefunden, wo zeiger unabdingbar sind: nämlich immer dann, wenn man grundlegende datenstrukturen, sortieralgorithmen etc. zum verständnis der internen abläufe selbst programmieren soll.😉



  • @Wade1234 sagte in Hängende Zeiger vermeiden:

    std::list?

    Ich schrieb "wenn man selbst Datenstrukturen (...) baut". List ist außerdem in beide Richtungen iterierbar, also doppelt verkettet (ich hatte oben absichtlich auch noch das "einfach" eingefügt).

    Wozu brauchst du bei einem Sortieralgorithmus Zeiger? std::sort (und eigentlich die gesamte STL) macht es vor - es nimmt einfach 2 Iteratoren.



  • @wob

    also wenn die daten in einem array sind, kann man doch prima mit zeigern arbeiten. ich hab hier irgendwo mal gelesen, dass das schneller geht.


  • Mod

    @Wade1234 sagte in Hängende Zeiger vermeiden:

    @wob

    also wenn die daten in einem array sind, kann man doch prima mit zeigern arbeiten. ich hab hier irgendwo mal gelesen, dass das schneller geht.

    Erklär doch mal, wie Iterator und Zeiger sich zueinander verhalten. Und was ein Iterator in einem Array ist. Oder wie std::sort ein Array sortieren kann.



  • Ich danke euch vielmals für die sehr wertvollen Antworten.

    Ich bin in meinem Projekt jetzt soweit, dass alle rohen Zeiger raus sind: Wenn ich noch wirklich einen Zeiger brauchte kam meist shared_ptr oder schlicht weg Referenzen zum Einsatz. C-arrays wurden zudem durch std::array ersetzt. Von mir selbst gibt es also kein new, delete oder carray mehr.

    Nur meine Audio Third Party Lib hat noch was mit rohen Zeigern drin und halt die Qt Widgets, aber die Qt Lib räumt ja da von selbst auf wenn ein Eltern Element gelöscht wird.

    Bei SmartPointer finde ich die Fehlermeldungen ziemlich strange teilweise, aber meist kommt dann doch auf den Fehler, ansonsten hilft StackOverflow.

    Kann es sein dass die Fehlermeldungen zu schwierig zu lesen sind, weil die SmartPointer mit Templates realisiert sind? An Templates wage ich mich noch überhaupt nicht ran, das sieht alles tierisch kompliziert aus. Es wird zwar immer geschrieben wie elegant man damit Sachen lösen kann, aber mich grault es jetzt schon vor kryptischen Fehlermeldungen.



  • @chris4cpp
    Ja, das ist bei templates leider so. Und wenn man sich damit noch nicht beschäftigt hat steht man wie der Ochs vorm Berg und kann mit der Fehlermeldung so gar nichts anfangen. Das kommt aber mit der Zeit.



  • @chris4cpp sagte in Hängende Zeiger vermeiden:

    Kann es sein dass die Fehlermeldungen zu schwierig zu lesen sind, weil die SmartPointer mit Templates realisiert sind? An Templates wage ich mich noch überhaupt nicht ran, das sieht alles tierisch kompliziert aus. Es wird zwar immer geschrieben wie elegant man damit Sachen lösen kann, aber mich grault es jetzt schon vor kryptischen Fehlermeldungen.

    Das ist ein Dauerbrenner in C++. Vor etlichen Jahren wollte man Concepts einführen, damit die Fehlermeldungen endlich sinnvoller lesbar sind. Concepts sind noch immer kein Teil der Sprache. Man sollte mit Templates selbst schreiben erst dann anfangen, wenn man die Sprache selbst halbwegs beherrscht.


  • Mod

    Du solltest übrigens nicht annehmen, dass du explizite Smartpointer brauchst, nur weil eine API Zeiger verlangt. Viele APIs brauchen Zeiger, aber nur wenige benutzen diese auch zur Ressourcenverwaltung. Oft geht es nur um die Übergabe von Daten im C-Stil.

    Ein bekanntes Beispiel ist die Stringverarbeitungs-API von C selbst. Die kannst du problemlos mit std::vector oder std::string benutzen, ohne irgendwo einen Smartpointer im Programm zu haben. Da lässt du den String/Vector alles intern regeln, und wenn du (aus unerfindlichen Gründen) unbedingt die Schnittstelle für C-Strings nutzen möchtest, dann würdest du einen Zeiger auf deren interne Datenfelder übergeben. Nicht ohne Zufall stellen diese Container eine passende Schnittstelle zur Verfügung:

        #include <vector>
        #include <string>
        #include <cstring>
        using namespace std;
         
        int main() {
        	string foo = "foo\0";
        	vector<char> bar(4);
        	strcpy(bar.data(), foo.data());
        	printf("%s", bar.data());
        }
    

    Das geht natürlich dann nicht mehr, wenn diese APIs selber Ressourcen verwalten und beispielsweise einen Handler rausgeben. Dann muss doch ein Smartpointer her (oder eine Wrapperklasse). Zum Beispiel die Dateizugriffsschnittstelle von C:

          void close_file(std::FILE* fp) { std::fclose(fp); }
    
          // ...
    
          std::unique_ptr<std::FILE, decltype(&close_file)> fp(std::fopen("demo.txt", "r"),
                                                               &close_file);
    

    (Beispiel von https://en.cppreference.com/w/cpp/memory/unique_ptr)



  • Kleine Optimierung:

    struct file_closer {
        void operator()(std::FILE* fp) const {
            std::fclose(fp);
        }
    };
    
    void test() {
        std::unique_ptr<std::FILE, file_closer> fp(std::fopen("demo.txt", "r"));
    }
    


  • @SeppJ sagte in Hängende Zeiger vermeiden:

    	string foo = "foo\0";
    

    Die '\0' ist doppelt gemoppelt und ein vector<char> bar(4) reicht dafür auch nicht.

    edit: Ja, ok, std::strcpy() hört natürlich bei der ersten '\0' auf.

    @chris4cpp Rohe Zeiger per se sind nicht böse. Rohe besitzende Zeiger sind böse.


  • Mod

    @Swordfish sagte in Hängende Zeiger vermeiden:

    Die '\0' ist doppelt gemoppelt und ein vector<char> bar(4) reicht dafür auch nicht.

    edit: Ja, ok, std::strcpy() hört natürlich bei der ersten '\0' auf.

    Auch die erste Aussage ist nicht richtig:
    https://ideone.com/wubSCh

    Tzz, tzz, tzz. Immer diese Kinners, die denken, sie könnten mir was von C-Strings erzählen…
    😛



  • @Swordfish sagte in Hängende Zeiger vermeiden:

    Die '\0' ist doppelt gemoppelt

    Steht und mir ist auch nicht klar warum Du die 0 explizit hinschreiben möchtest.



  • @SeppJ: Hattest du den falschen Code gepostet (mit "foo\n" anstatt "foo\0")?

    -> korrigierter Code

    Ein String kann zwar '\0' speichern, aber der Konstruktor string(const char *) liest nur bis zu diesem Zeichen (exklusiv). @Swordfish hat also Recht mit seiner Aussage. 😉



  • Danke euch für die Zusatzinfos, das mit dem Deleter bei SmartPointern kannte ich auch noch nicht, das ist ja genial.

    Hier hatte ich auch schon mal die data() Methode vom std::vector im Einsatz. Das ist auch eine Stelle wo ich vorher nur rohe Zeiger auf ein Char CArray eingesetzt hatte:

    Framebuffer::Framebuffer(int width, int height)
    	:
    	width_(width),
    	height_(height),
    	length_(width * height << 2),
    	yoffset_(width << 2),
    	buffer_(width * height << 2),
    	color_(Color(0, 0, 0))
    {
    	img_ = QImage(buffer_.data(), width_, height_, Framebuffer::format_);
    }
    
    

    Toll was man alles bei so eine etwas größeren Testprojekt dazulernt. Ich hatte ja schon diverse Male mit C++ und auch C angefangen(früher mal Assembler auf dem Amiga), aber dort nur stupide alle Möglichen Sachen in der Konsole ausprobiert und dann genauso schnell wieder alles vergessen was ich gelernt habe. Hier bei dem Projekt ärgert es mich richtig wenn ich nicht weiter komme und grobe Schnitzer drin sind, dann will ich es besser machen.



  • @chris4cpp sagte in Hängende Zeiger vermeiden:

    Wenn ich noch wirklich einen Zeiger brauchte kam meist shared_ptr (...) zum Einsatz.

    Spannend. Im Regelfall benutzt man für besitzende Zeiger std::unique_ptr. Shared_ptr braucht man (=ich, Schluss von mir auf andere...) eher sehr selten, nämlich wenn die Ressourcen wirklich keinen eindeutigen Besitzer haben. Also: bist du sicher, dass du wirklich überall, wo du shared_ptr benutzt, wirkich shared_ptr statt unique_ptr (bzw. rohe, nicht besitzende Zeiger) brauchst?



  • @wob Man kann mit shared_ptr viel einfacher die Scheuklappen aufsetzen und Komponenten nur für sich genommen betrachten und entwickeln. "Geborgte" Pointer sind ... oft keine so gute Idee wie sie anfangs zu sein scheinen.

    Und mit "geborgte" Pointer meine ich: rohe, nicht besitzende Zeiger die länger leben als der Funktionsaufruf bei dem sie übergeben werden. Also die Sorte wo ich einem Objekt einen Zeiger gebe, das merkt sich den Zeiger dann, und ich bin dann verantwortlich dafür das Objekt lange genug am Leben zu halten.



  • @wob Nein, ich bin mir nicht sicher ob nicht auch ein unique ausgereicht hätte. Der Shared Pointer klang von der Beschreibung her sehr einfach, weil ich hier wirklich die Verantwortung sehr simple abgeben kann.


Anmelden zum Antworten