Verweis uaf eine gelöschte Funktion in std::unique_ptr



  • Hallo zusammen,

    ich stelle gerade mein "Lernprojekt" um auf unique_ptr und bekomme nun folgenden Fehler:

    Fehler C2280 "std::unique_ptr<Abstract,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)" : Es wurde versucht, auf eine gelöschte Funktion zu verweisen Projekt1 c:\program files (x86)\microsoft visual studio 14.0\vc\include\xmemory0 737

    Hier ein kleines Beispiel dazu:
    Abstract.h:

    #pragma once
    class Abstract
    {
    public:
    	virtual ~Abstract() = default;
    	Abstract(Abstract& other) = default;
    	Abstract(Abstract&& other) = default;
    	Abstract& operator=(Abstract& other) = default;
    	const int Number;
    
    protected:
    	Abstract(int number) :Number(number){};
    };
    

    Example.h:

    #pragma once
    #include <memory>
    #include "Abstract.h"
    #include <vector>
    
    class Example
    {
    public:
    	Example(){};
    	~Example() = default;
    	std::vector<std::unique_ptr<Abstract>> Get() const { return _items; };
    
    private:
    	std::vector<std::unique_ptr<Abstract>> _items;
    };
    

    Ich denke das er etwas kopieren will, dies aber von unique_ptr nicht unterstützt wird. Passiert dies bei Get() und wenn ja, wie kann man das sauber lösen?

    Schöne Grüße und vielen Dank
    Quaneu


  • Mod

    Dein get gibt Kopien deiner unique_ptr zurück. Das ist ein Widerspruch in sich. Gib doch eine Referenz auf den Vector zurück! Einen ganzen Vector aus einer Getter-Funktion heraus zu kopieren ist sowieso nicht gerade die feine Art.



  • Vielen Dank für die schnelle Antwort. Ist dann die Referenz auf den Vector die "feine" Art oder ist dies nur ein Fehler lösen?
    Es soll außerhalb der Klasse mit diesen Daten gearbeitet werden, daher ist es doch "normal", wenn man einen Vector nach außen gibt, oder?



  • Std::vector verwaltet Daten dynamisch und arbeitet intern auch mit Pointern. Unique_ptr sind jetzt aber eben dazu da "einzigartig" zu sein, daher geht eine Kopie nicht. Wenn du auch wo anders auf den Daten arbeiten willst ist der unique_ptr evt. die falsche Wahl und du möchtest eher std::vector<Abstract> verwenden. Oder einen shared_ptr? Kommt halt drauf an, was du vor hast.

    Was man bei gettern ganz gerne macht ist eine const Referenz zurück geben. Dann kann man sich nach dem get immer noch überlegen ob man eine Kopie braucht um die Daten zu manipulieren (wobei auch hier gilt, Kopie von unique_ptr ist nicht 😉 ).



  • ist es doch "normal", wenn man einen Vector nach außen gibt, oder?

    Normal ist das schon. Wichtig ist nur die Frage, was man nach außen gibt - den bereits instanzierten Vektor oder einen kopierten Vektor.

    int func()
    {
       int variable_i;
       return variable_i;
    }
    

    Hier gibst Du ja nicht variable_i nach draußen, sondern eine Kopie von variable_i, denn nach Verlassen von func() wird die auf dem Stack befindliche Variable freigegeben. Dasselbe gilt, wenn Du eine bereits vorhandene Variable übergibt, sofern im Funktionskopf weder eine Referenz noch ein Zeiger als Rückgabe angegeben wird. Denn wo sonst soll func den Rückgabewert hinstecken?

    Wenn nun aber kopiert (oder gemoved) wird, dann wird der komplette Objektkopier-/move-Prozess durchlaufen. Kopiert/Gemoved wird aber im ersten Schritt nur der Vector, unique_ptr bleibt gleich (oder kopiert std::vector= tief?). Selbst wenn vector= tief kopieren würde, würde dies bei unique_ptr nicht funtionieren.

    in Deinem Fall wurde der Deleter von unique_ptr aufgerufen, also default_delete<T> - der sowohl für die Kopie als auch für das Original identisch sind. Das heißt, der Deleter arbeitet, obwohl (hier die nach außen gegebene Kopie) noch vorhanden sein soll --> schlecht. Deswegen der Fehler, die Funktion, auf welche verwiesen wurde, war bereits gelöscht.

    Also entweder shared_ptr verwenden, dann kann auch die Kopie auf den Inhalt zugreifen (außer vector= kopiert flach, dann weiß ich nicht wie das funktioniert), der shared_ptr_Destructor kommt erst, wenn das letzte besitzende Objekt zerstört wird oder die Referenz übergeben. Bei der Übergabe einer Referenz wird nämlich nicht kopiert (da die Referenz nur ein "anderer Name" für denselben Speicherplatz ist - keine Kopie, kein Move).

    Dennoch frage ich mich, wann Du das Object gelöscht hast, weswegen die Fehlermeldung kam. Irgendwann hast Du einen Scope verlassen, weswegen der unique_ptr-Deleter aufgerufen worden ist, das muss irgendwo im Programmverlauf der Fall gewesen sein.



  • @&(*null)->Hallo:
    Dennoch frage ich mich, wann Du das Object gelöscht hast, weswegen die Fehlermeldung kam. Irgendwann hast Du einen Scope verlassen, weswegen der unique_ptr-Deleter aufgerufen worden ist, das muss irgendwo im Programmverlauf der Fall gewesen sein.

    Mein Beispiel ist der ganze Code, bis auf eine main.cpp, die aber nur 0 zurück gibt. Diese beidenKlassen führen zu dem Fehler.

    @Schlangenmensch

    All meine Daten sollen "immutable" sein, da ich eine Datei lese und diese nur anzeigen will. Daher will ich gar nicht, dass sie danach manipuliert werden.

    Die Klasse Abstract ist eine Basisklasse für viele andere Klassen, daher benutze ich eigentlich auch den std::unique_ptr, wegen polymorphie.



  • &(*null)->Hallo schrieb:

    Dennoch frage ich mich, wann Du das Object gelöscht hast, weswegen die Fehlermeldung kam. Irgendwann hast Du einen Scope verlassen, weswegen der unique_ptr-Deleter aufgerufen worden ist,

    Du hast die Fehlermeldung nicht richtig gelesen oder verstanden.



  • Quaneu schrieb:

    All meine Daten sollen "immutable" sein, da ich eine Datei lese und diese nur anzeigen will. Daher will ich gar nicht, dass sie danach manipuliert werden.

    Die Klasse Abstract ist eine Basisklasse für viele andere Klassen, daher benutze ich eigentlich auch den std::unique_ptr, wegen polymorphie.

    Ja, aber mit folgendem:

    std::vector<std::unique_ptr<Abstract>> Get() const { return _items; }
    

    erzeugst du eine KOPIE des Vektors! Du versuchst also beim Aufruf, den vector samt Inhalt komplett zu duplizieren. D.h. du versuchst somit auch, die unique_ptr zu kopieren.

    Eigentlich willst du ja nur, dass man den Inhalt sieht und gar keine Kopie erzeugen. Daher einfach folgendes schreiben:

    const std::vector<std::unique_ptr<Abstract>>& Get() const { return _items; }
    

    Oder aber du machst es mehr im STL-Stil und machst begin und end des vectors nach außen verfügbar.



  • Wenn man den Vector nicht als Referenz übergeben würde, würde der Aufruf Get() auch nur einmal funktionieren => Man muss es als Referenz zurück geben.
    Danke schon mal dafür.



  • Update:
    Wenn ich nun eine Referenz zurück gebe geht es leider auch nicht, sobald ich eine Instanz von Example anlege.

    const std::vector<std::unique_ptr<Abstract>>& Get() const { return _items; };
    
    main.cpp
    #include "Example.h"
    
    int main()
    {
    	Example example;
    	auto items = example.Get();
    	return 0;
    }
    

    Damals hatte ich noch eine Vermutung, aber jetzt weiß ich überhaupt nicht mehr woran dies liegen kann...



  • auto items = example.Get();
    denk mal nach, was hier passiert.



  • Also die Funktion gibt eine Referenz zurück, dabei sollte nichts "schlimmes" passieren, danach wird zugewiesen und das sollte hoffentlich nicht mit kopieren passieren. Aber ich denke das stimmt nicht. Was pssiert denn und wie macht man es denn richtig?



  • Gleichheitszeichen machen was? Richtig, Kopien!
    Wieso sollte = nicht kopieren? Wenn du a=b; schreibst mit zwei ints, erwartest du doch auch eine Kopie, nach der a von B unabhängig ist.

    Mit dieser Anweisung versucht du also erneut, den vector zu kopieren.

    Wenn du die Referenz, die rechts des =-Zeiches steht, behalten willst, musst du das explizit mitteilen:

    auto &items = example.Get();
    

    Dadurch kopierst du nur die Referenz, aber nicht den Inhalt.

    Ich kann nur raten, mal einfach ein wenig mit ints herumzuspielen, z.B. so

    int main (){
      int a = 42;
      int &aref = a;
      ... Hier irgendwie die Variablen manipulieren ...
      std::cout << a << '\n';
    }
    


  • Ich habe das Gefühl, dass die Verwendung von auto bei Anfängern eher Verwirrung stiftet. Vielleicht wäre es für den Anfang gut einfach den gewünschten Typen hinzuschreiben.

    const std::vector<std::unique_ptr<Abstract>>& items = example.Get();
    


  • Ich habe mir eben aus diesem Grund ein kleines Beispiel gebaut und habe da auch so einiges ausprobiert. Aber leider bin ich von falschen Tatsachen ausgegangen, da ich dachte das bei = entweder kopiert oder gemoved wird.

    Mittlerweile zweifel ich jedoch grundsätzlich an der Verwendung von unique_ptr in einem Vector, wenn dieser nach außen gegeben wird...
    Wenn jemand zweimal den Eintrag n aus dem Vector holen will, geht das nicht mehr. Denn beim ersten mal muss er ihn mit move aus dem Vector holen (kopieren geht ja nicht) und beim zweiten mal ist er leer... (Wahrscheinlich ist dies aber eine neue Frage)



  • Die Frage die sich bei der Wahl von Smart Pointern stellt ist halt, wofür man sie benutzen will.

    Was zum Beispiel bei dir gehen sollte (nicht probiert)

    for(auto& abstract : example.Get()){
       abstract->someFunction();
    }
    

    Also, wenn du die Daten wirklich nur ausgeben willst, reicht die const reference.



  • Ich gebe diesen Vector ja nach außen. Damit ein Anwender damit arbeiten kann bzw. die Daten analysieren kann.
    Wenn der Anweder jedoch folgendes machen würde:

    const std::vector<std::unique_ptr<Abstract>>& items = example.Get();
    
    for(auto& abstract : items ){
       abstract->someFunction();
    }
    
    ...
    
    for(auto& abstract : items ){
       abstract->someFunction();
    }
    

    Würde die zweite Schleife nur noch leere "Hüllen" liefern... Dies finde ich sehr unschön, daher denke ich, dass es so gar keinen Sinn macht.



  • Quaneu schrieb:

    Wenn der Anweder jedoch folgendes machen würde:

    const std::vector<std::unique_ptr<Abstract>>& items = example.Get();
    
    for(auto& abstract : items ){
       abstract->someFunction();
    }
    
    ...
    
    for(auto& abstract : items ){
       abstract->someFunction();
    }
    

    Würde die zweite Schleife nur noch leere "Hüllen" liefern...

    Das ist falsch.

    Dies finde ich sehr unschön, daher denke ich, dass es so gar keinen Sinn macht.

    Wenn dem so wäre, wäre es unschön. Es ist aber nicht so. (hast du es ausprobiert?)



  • Quaneu schrieb:

    Ich gebe diesen Vector ja nach außen. Damit ein Anwender damit arbeiten kann bzw. die Daten analysieren kann.
    Wenn der Anweder jedoch folgendes machen würde:

    const std::vector<std::unique_ptr<Abstract>>& items = example.Get();
    
    for(auto& abstract : items ){
       abstract->someFunction();
    }
    
    ...
    
    for(auto& abstract : items ){
       abstract->someFunction();
    }
    

    Würde die zweite Schleife nur noch leere "Hüllen" liefern... Dies finde ich sehr unschön, daher denke ich, dass es so gar keinen Sinn macht.

    Woher kommt das "wissen", dass die unique_ptrs in items nach der ersten schleife leer sind?
    Hast du das ausprobiert oder vermutest du nur?

    Im deinem Beispiel ist die schleife das gleiche wie

    for(std::unique_ptr<Abstract>& abstract : items ){
       abstract->someFunction();
    }
    

    Da wird nichts gemoved.



  • Entschuldige Du hast recht, das Beispiel war falsch. Hatte es mit items.at() ausprobiert. Und da geht es nicht, wenn man sich zweimal den selben Eintrag holt.


Log in to reply