C-Pointer in C++ Klasse verwalten



  • Hallo,
    ich nutze eine C-Schnitte von der ich einen C-Struct erhalte. Der C-Struct hat einen Pointer und eine Größe. Der Pointer wird einem Mat-Objekt von OpenCV übergeben, der laut Doku allerdings nicht gemanaged wird, da er manuell übergeben wurde. Irgendwie muss ich also den C-Pointer verwalten.
    Ich habe daher an ein C++ Objekt gedacht, der mindestens so lange lebt wie das Mat-Objekt von OpenCV:

    Header:

    #ifndef WRAPPER_H
    #define WRAPPER_H
    
    class Wrapper
    {
    public:
        Wrapper();
        Wrapper(Wrapper &&other) noexcept;
        Wrapper(Wrapper &other) = delete;
        Wrapper(char *data);
        ~Wrapper();
    
        Wrapper &operator=(const Wrapper &other) = delete;
        Wrapper &operator=(Wrapper &&other) noexcept;
    
    private:
        char *data;
    };
    
    #endif // WRAPPER_H
    
    

    CPP:

    #include "wrapper.h"
    
    Wrapper::Wrapper() : data(nullptr)
    {
    }
    
    Wrapper::Wrapper(Wrapper &&other) noexcept
    {
        data = other.data;
        other.data = nullptr;
    }
    
    Wrapper::Wrapper(char *data) : data(data)
    {
    }
    
    Wrapper::~Wrapper()
    {
        delete data;
    }
    
    Wrapper &Wrapper::operator=(Wrapper &&other) noexcept
    {
        // Check for self-assignment
        if (&other == this)
        {
            return *this;
        }
    
        delete data;
        data = other.data;
        other.data = nullptr;
    
        return *this;
    }
    
    

    Meine Frage ist nun, ob die Lösung ok bzw. gut ist.

    Danke im Voraus und viele Grüße,
    Steffo



  • C++ hat schon Smart Pointer:
    Schau mal std::make_shared / std::make_unique an.



  • @out sagte in C-Pointer in C++ Klasse verwalten:

    C++ hat schon Smart Pointer:
    Schau mal std::make_shared / std::make_unique an.

    Die kenne ich. Aber wie nutze ich die, wenn ich von einer Lib einen Roh-Pointer bekomme? std::make_shared() alloziiert ja Speicher, aber ich will ja nichts allozieren, sondern einen rohen Pointer zuweisen.



  • delete data sieht für mich nicht richtig aus, es gibt doch mit Sicherheit eine entsprechende Funktion zum Freigeben (Release o.Ä,) die von einem Wrapper aufgerufen werden kann.
    Wenn man Speicher nicht selber angefordert hat, sollte man ihn auch nicht löschen, wenn es Alternativen gibt.
    Allerdings verstehe ich den Satz auch nicht:
    @Steffo sagte in C-Pointer in C++ Klasse verwalten:

    Der Pointer wird einem Mat-Objekt von OpenCV übergeben, der laut Doku allerdings nicht gemanaged wird, da er manuell übergeben wurde.



  • @yahendrik sagte in C-Pointer in C++ Klasse verwalten:

    delete data sieht für mich nicht richtig aus, es gibt doch mit Sicherheit eine entsprechende Funktion zum Freigeben (Release o.Ä,) die von einem Wrapper aufgerufen werden kann.

    Nein, das Struct-Objekt hat intern einfach nur einen rohen Pointer.

    Wenn man Speicher nicht selber angefordert hat, sollte man ihn auch nicht löschen, wenn es Alternativen gibt.

    Das ist eine C-Schnittstelle und ich habe den Speicher indirekt selbst angefordert. Konkret fordere ich vom Kamera-Treiber ein Bild an. Ich bekomme ein Struct-Objekt mit einem Pointer und einer Größenangabe. Die Daten übergebe ich dann dem Mat-Konstruktor von OpenCV. Da OpenCV den Speicher in diesem Fall nicht automatisch dealloziiert, muss ich mich um die Speicherbereinigung kümmern. Siehe https://docs.opencv.org/3.1.0/d3/d63/classcv_1_1Mat.html#a5fafc033e089143062fd31015b5d0f40



  • @Steffo sagte in C-Pointer in C++ Klasse verwalten:

    Die kenne ich. Aber wie nutze ich die, wenn ich von einer Lib einen Roh-Pointer bekomme?

    Du kannst auch einen Zeiger übergeben, du musst kein make_shared aufrufen.



  • Du kannst auch einen Deleter für unique und smart_ptr definieren.



  • @Mechanics sagte in C-Pointer in C++ Klasse verwalten:

    @Steffo sagte in C-Pointer in C++ Klasse verwalten:

    Die kenne ich. Aber wie nutze ich die, wenn ich von einer Lib einen Roh-Pointer bekomme?

    Du kannst auch einen Zeiger übergeben, du musst kein make_shared aufrufen.

    Das hatte ich auch schon versucht:

    auto* data1 = static_cast<char*>(malloc(1000));
    std::memset(data1, '-', 1000);
    // std::unique_ptr<char*> u(data1); // Kompiliert nicht
    std::unique_ptr<char*> u(&data1);// Speicherzugriffsfehler
    


  • @Steffo sagte in C-Pointer in C++ Klasse verwalten:

    Das hatte ich auch schon versucht:

    auto* data1 = static_cast<char*>(malloc(1000));
    std::memset(data1, '-', 1000);
    // std::unique_ptr<char*> u(data1); // Kompiliert nicht
    std::unique_ptr<char*> u(&data1);// Speicherzugriffsfehler
    

    Wozu soll das gut sein?



  • @manni66 sagte in C-Pointer in C++ Klasse verwalten:

    @Steffo sagte in C-Pointer in C++ Klasse verwalten:

    Das hatte ich auch schon versucht:

    auto* data1 = static_cast<char*>(malloc(1000));
    std::memset(data1, '-', 1000);
    // std::unique_ptr<char*> u(data1); // Kompiliert nicht
    std::unique_ptr<char*> u(&data1);// Speicherzugriffsfehler
    

    Wozu soll das gut sein?

    Hi Manni,
    das ist nur ein minimales Beispiel für mein oben beschriebenes Problem. Mir würde es wirklich weiterhelfen, wenn mir jemand erklären würde, weshalb der obige Code nicht funktioniert.



  • @Steffo
    Wenn man Stackadressen löscht, knallt es halt.



  • @manni66 sagte in C-Pointer in C++ Klasse verwalten:

    std::unique_ptr<char*> u(&data1);

    Du willst eher
    std::unique_ptr<char> u(data1);



  • @Mechanics sagte in C-Pointer in C++ Klasse verwalten:

    @manni66 sagte in C-Pointer in C++ Klasse verwalten:

    std::unique_ptr<char*> u(&data1);

    Du willst eher
    std::unique_ptr<char> u(data1);

    Ich nicht und er auch nicht (zumindest nicht, wenn der Speicher mit malloc angelegt wurde).



  • "eher" 😉



  • Wenn du den smart pointer mit malloc fütterst musst du den Deleter selbst definieren. Siehe den zweiten Template Parameter von unique_ptr



  • @Steffo sagte in C-Pointer in C++ Klasse verwalten:

    Das ist eine C-Schnittstelle und ich habe den Speicher indirekt selbst angefordert.

    Ein paar Dinge dazu.

    1. delete ptr ist falsch wenn es sich um ein Array handelt, in dem Fall müsstest du delete[] ptr schreiben
    2. Wenn der Speicher nicht mit new T[] angefordert wurde, dann sollte er auch nicht mit delete[] ptr gelöscht werden.
    3. "Indirekt selbst angefordert" kann alles mögliche heissen. Wenn du den Speicher direkt selbst anforderst, dann ist es OK wenn du ihn selbst löscht - auf welchem Weg sollte dann klar sein anhand dessen wie du ihn angefordert hast. Wenn du den Speicher dagegen von der C Library bekommst, dann musst du in der Dokumentation der C Library nachgucken wie du ihn freigeben sollst.

    delete ptr ist aber mit an Sicherheit grenzender Wahrscheinlichkeit falsch.

    Davon abgesehen:

    • Wrapper(Wrapper &other) = delete; ist OK aber nicht nötig, da du ja bereits einen move-Ctor definierst, und dieser verhindert die implizite Definition eines Copy-Ctors. Weiters ist es unüblich beim Copy-Ctor das const wegzulassen.
    • Wrapper(char *data); ist recht ungünstig. Du solltest diesen Ctor explicit machen. Sonst kann man nämlich sowas wie Wrapper w = "trallala"; schreiben. Und das sollte vermutlich eher nicht gehen.
    • Was deinen Move-Assignment-Operator angeht: Das kann man einfacher Lösen. Stichwort Copy-and-Swap. (Hat seine ursprünge zwar in Zeiten wo es noch kein Move gab, aber man kann es genau so gut für Move-Assignment verwenden.)

    Und p.s.: Ja, über unique_ptr wäre es vermutlich noch einfacher. Die oben genannten Dinge zu beheben wäre aber vermutlich trotzdem eine gute Übung.



  • @Steffo sagte in C-Pointer in C++ Klasse verwalten:

    #include <memory>
    #include <cstring>
    
    int main() {
        auto* data1 = static_cast<char*>(malloc(1000));
        std::memset(data1, '-', 1000);
        std::unique_ptr<char, decltype(&free)> u(data1, &free);
    }
    

    Wenn der Speicher mit malloc angefordert wurde, löscht du mit free. Das geht so wie hier gezeigt.

    Wenn du eine andere Funktion für das Freigeben aufrufen musst (die C-Funktionen haben ja gerne eigene Wrapper dafür), dann schau dir am besten das close_file-Beispiel auf https://en.cppreference.com/w/cpp/memory/unique_ptr an.

    Nimmst du statt unique_ptr einen shared_ptr, wird es etwas einfacher, weil dann der Typ des Löschers typgelöscht ist (brrr... was ist type erasure auf Deutsch?), d.h. du hast nicht den 2. Template-Parameter.



  • Danke, für eure Antworten! 🙂
    Mein Kollege und ich haben nun eine Lösung mit std::unique_ptr und free gebastelt. Geht in die Richtung der Lösung von @wob. 🙂


  • |  Mod

    @Steffo sagte in C-Pointer in C++ Klasse verwalten:

    Danke, für eure Antworten! 🙂
    Mein Kollege und ich haben nun eine Lösung mit std::unique_ptr und free gebastelt. Geht in die Richtung der Lösung von @wob. 🙂

    Hoffentlich mit zustandslosem Funktor statt extra Funktionszeiger, der std::unique_ptr nur unnötig groß macht (und u.U. weniger effizienten Code erzeugt).