Pointer auf operator delete[]



  • Michael E. schrieb:

    Wie rufst du denn delete auf?

    Im Destruktor. I.d.R. wird nicht einfach der Operator delete, sondern eine umfangreichere delete Funktion aufgerufen:

    virtual ~MyClass()
    {
    	this->Delete(this->obj);
    }
    

  • Administrator

    Und was ist Delete für ein Objekt? Wie übergibst du den operator delete ?

    Ganz allgemein:
    Wieso machst du nicht noch einen Konstruktor, wo keine Delete-Funktion übergeben wird und das Delete Objekt auf nullptr oder sonst einen ungültigen Wert setzt? Im Destruktur prüfst du dann die Gültigkeit und falls es ungültig ist, löschst du es mit delete . Dürfte auch für den Anwender angenehmer sein, als dass er den operator delete übergeben muss.

    Grüssli



  • Das ganze sehe so aus. Den Operator kann man doch auch als default Parameter übergeben:

    template<class Class>
    class MyClass
    {
    private:
    	Class*	obj;
    	void (__cdecl *Delete)(Class*);
    
    public:
    	template<class Signature>
    	MyClass( 
    		Class* 	  Obj,
    		Signature DelFunc = ::operator delete
    	)
    	{
    		this->Delete = DelFunc;
    	}	
    
    	virtual ~MyClass()
    	{
    		this->Delete(this->obj);
    	}
    };
    

  • Administrator

    FrEEzE2046 schrieb:

    Das ganze sehe so aus. Den Operator kann man doch auch als default Parameter übergeben:

    Du verpasst damit aber die Möglichkeit, dass per delete auch standardmässig der operator delete der Klasse selbst aufgerufen wird, falls einer definiert ist 😉

    Zudem bin ich gerade nicht ganz sicher darüber, wie operator delete von der Standardbibliothek definiert sein muss. Auch gibt es mehrere davon. Mal schauen, ob da jemand anderes gerade noch zusätzliche Informationen auswendig kennt 🙂

    Grüssli



  • Zumindest bei MSDN ist nur die Rede von einer Überladung.

    Wo liegt denn grundsätzlich das Problem? Der Operator delete ist folgendermaßen forward deklariert:

    void operator delete(void*);
    

    Warum nimmt er diese Funktion nicht, void Func(void*) aber schon?


  • Administrator

    FrEEzE2046 schrieb:

    Zumindest bei MSDN ist nur die Rede von einer Überladung.

    Wo bitte soll dies im Artikel stehen? Im Standard steht es zudem anders.

    FrEEzE2046 schrieb:

    Wo liegt denn grundsätzlich das Problem? Der Operator delete ist folgendermaßen forward deklariert:

    Forward deklariert? Hast du dies selber gemacht? Eine Deklaration von operator delete ist implizit immer in jeder TU schon vorhanden!

    Wieso willst du diesen unnötigen, komplizierten und beschränkteren Weg gehen? Wie ich schon sagte, wenn du delete aufrufst, hast du mehr Standardverhalten, als wenn du probierst diese Funktion irgendwie zu speichern.

    Grüssli



  • Im Endeffekt möchte ich doch nur wissen, wo der große Unterschied zwischen

    void operator delete(void*) und
    void Func(void*)

    ist, da letzteres funktioniert und operator delete nicht. Mehr nicht. Ist doch legitim wisse zu wollen, woher der Compiler-Fehler kommt.
    Ich kann das ganze natürlich auch anders lösen, aber interessieren tut mich die Fehler-Ursache schon.

    Zudem weiß ich nicht, wie ich bei deiner Version dann explizit den operator delete oder den operator delete[] aufrufen soll.


  • Administrator

    FrEEzE2046 schrieb:

    ..., aber interessieren tut mich die Fehler-Ursache schon.

    Kannst du ein vollständig, kompilierbares und kleines Beispielprogramm zeigen, welches den Fehler reproduziert? Wenn diese Vorlesung weiterhin so stink langweilig ist, dann kann ich mir dies in den nächsten Stunden noch anschauen 😉

    Grüssli



  • Übrigens ist operator delete lediglich eine Speicherdeallokationsfunktion, es wird nicht der Destruktor aufgerufen. operator delete(x) ist nicht dasselbe wie delete x .



  • Okay, soweit verunstaltet, dass es fast wie eine SmartPointer Implementation ausschaut ;-). Naja, so ähnlich ist es ja auch fast.
    Hab's mal heruntergebrochen auf nen ganz normalen int Typen. Die Datenbankobjekte sagen euch sowieso nichts, da firmenintern.

    #include <iostream>
    
    using std::cout;
    
    template<class Class>
    class MyClass
    {
    private:
    	Class* obj;
    	void (__cdecl *const Delete)(Class*);
    
    public:
    	template<class Signature>
    	MyClass(Class* Obj, Signature DelFunc) : Delete((void (__cdecl*)(Class*))DelFunc), obj(Obj) {}	
    
    	virtual ~MyClass() {
    		this->Delete(this->obj);
    	}
    };
    
    void Delete1(void* memory)
    {
    	delete memory;
    }
    
    void Delete2(int* memory)
    {
    	delete memory;
    }
    
    int* Delete3(void* memory)
    {
    	delete memory;
    	return 0;
    }
    
    int* Delete4(int* memory)
    {
    	delete memory;
    	return 0;
    }
    
    int main()
    {
    	MyClass<int> myc1(new int, Delete1);
    	MyClass<int> myc2(new int, Delete2);
    	MyClass<int> myc3(new int, Delete3);
    	MyClass<int> myc4(new int, Delete4);
    
    	// Error	1	error C2660: 'MyClass<Class>::MyClass' : function does not take 2 arguments
    	MyClass<int> myc5(new int, operator delete);		
    
    	return 0;
    }
    

    Bashar schrieb:

    Übrigens ist operator delete lediglich eine Speicherdeallokationsfunktion, es wird nicht der Destruktor aufgerufen. operator delete(x) ist nicht dasselbe wie delete x .

    Ist hier auch nicht anders gewünscht. Aber mal was anderes: Was passiert eigentlich wenn man delete[] für ein "nicht-array" aufruft bzw. sollte man dann nicht immer delete[] aufrufen, wenn man nicht weiß, ob es sich um ein array handelt?



  • Undefiniertes Verhalten, was sonst 🙂



  • Bashar schrieb:

    Undefiniertes Verhalten, was sonst 🙂

    Ich habe mich damit noch nicht beschäftigt, aber delete[] muss ja irgendwie feststellen, wie viele Bytes es denn freigeben muss.


  • Administrator

    Professor wollte dann doch noch, dass wir nicht gelangweilt herumsitzen. Haben wohl zuviele in den Laptop getippt, statt aufgepasst 😃

    @FrEEzE2046,
    Was soll das bitte für ein Cast in deinem Konstruktor sein? Das ist ja sowas von abartig! Dadurch kann man wunderschönes undefiniertes Verhalten auslösen und der Kompiler winkt alles brav durch. Nimm diesen Cast da weg!
    Und nun wirst du sicher sagen: "Aber dann kommen Kompilerfehler!"
    So löst man aber keine Kompilerfehler und erst recht nicht diese. Ich empfehle dir, dass du mal boost::function , bzw. std::tr1::function anschaust. Das ist der bessere und saubere Weg.

    Wenn du das entsprechend korrigiert hast, wirst du übrigens auch sehen, dass der Kompiler meckert, weil operator delete ein überladener Operator ist. Genau wie ich es gedacht habe 😉

    FrEEzE2046 schrieb:

    ..., aber delete[] muss ja irgendwie feststellen, wie viele Bytes es denn freigeben muss.

    Wie delete[] den Speicher freigeben muss, gibt der Standard keine Vorgaben. Es ist schlicht und einfach kompilerabhängig. Und daher ist der Aufruf von delete[] , auf einen Speicherbereich der per new geholt wurde, undefiniert. Gleiches gilt im umgekehrten Fall ( delete und new[] ). Da bringt es nichts lange rumzuphilosophieren, wie ein Betriebsystem oder Kompiler dies nun tut. Sobald du sowas einsetzt, hast du undefiniertes Verhalten. Es mag auf diesem System mit diesem Kompiler funktionieren und beim nächsten Wechsel von einer dieser Variablen kracht das ganze Programm oder es gibt ein Speicherfehler, der aber dann wo völlig anders auftritt. Undefinierte Fehler zu beheben ist etwas vom mühsamsten, daher gleich erst gar nicht erlauben, dass solche Fehler auftreten könnten.

    Grüssli



  • Ich wollte eigentlich nicht extra eine Functor-Klasse bemühen, da dass aus meiner Sicht hier nicht nötig ist.

    Der Cast ist widerlich, ich weiß. Aber warum stellst dass ein so großes Problem dar?

    Im Endeffekt bleibt die Funktion doch immer wieder gleich. Wir haben:

    TypA Function(TypB);
    

    Das bringe ich immer in die Form:

    void Function(Class*);
    

    So. Da in den Klammern immer ein Pointer und damit entsprechend der Wortbreite des Professors, viele Byte auf den Stack gepushed werden sehe ich hier keinen Grund für undefiniertes Verhalten. Das bestätigt auch das Assembly-Listing.

    Schlussendlich ist auf unterster Ebene doch egal, um was für einen "Typ" es sich hier handelt, so lange die Größe gleich bleibt.

    Jede Funktion liegt an einer speziellen Adresse die der Kompiler kennen muss. Er weiß nun auch wie viele Byte er auf den Stack zu pushen hat. Und dass der Rückgabewert nun void ist ... na und? Dass bedeutet nur, dass ich den Wert aus dem eax Register nicht als return value benutze.

    Ich sehe da nicht wirklich das Problem.



  • FrEEzE2046 schrieb:

    Ich wollte eigentlich nicht extra eine Functor-Klasse bemühen, da dass aus meiner Sicht hier nicht nötig ist.

    Der Cast ist widerlich, ich weiß. Aber warum stellst dass ein so großes Problem dar?

    Sorry wenn ich grad mal völlig off topic bin, aber hier stellt was anderes ein großes Problem dar: http://www.dass-das.de/


  • Administrator

    Bisschen undefiniertes Verhalten gefälligst?

    template<class Class> 
    class MyClass 
    { 
    private: 
        Class* obj; 
        void (__cdecl *const Delete)(Class*); 
    
    public: 
        template<class Signature> 
        MyClass(Class* Obj, Signature DelFunc) : Delete((void (__cdecl*)(Class*))DelFunc), obj(Obj) {}    
    
        virtual ~MyClass() { 
            this->Delete(this->obj); 
        } 
    }; 
    
    void foo(short, char, int)
    {
    }
    
    void bar(MyClass<int>, MyClass<void>)
    {
    }
    
    int main() 
    {
        // Mal schauen ...
        MyClass<int> myc1(new int, &foo); 
        MyClass<int> myc2(new int, &bar);
    
        // Oder vielleicht?
        MyClass<int> myc3(new int, 'c'); 
        MyClass<int> myc4(new int, "error");
        MyClass<int> myc5(new int, 2343577); 
    
        // Und solchen Zucker kann man auch erzeugen.
        MyClass<int> myc6(new int, &myc1);
    
        return 0; 
    }
    

    Es kompiliert, man kann es ausführen, allerdings besteht die Möglichkeit, dass man damit den Weltuntergang heraufbeschwört 😃

    Du umgehst eben jegliche Typsicherheit. Das ist ja auch ein Grund, wieso man C Cast oder auch reinterpret_cast vermeiden sollte.

    Grüssli



  • ah,
    jetzt verstehen wir uns. Wenn ich natürlich gar keine Funktion, sondern irgengwas anderes, übergebe kommt natürlich irgendetwas undefiniertes heraus.

    Lässt sich aber eigentlich leicht umgehen:

    template<class Signature>
    class IsFunction;
    
    template<typename R, typename P0>
    class IsFunction<R (__cdecl*)(P0)> {};
    
    // [...]
    
    // error C2079: 'MyClass' uses undefined class
    MyClass<int> myc5(new int, "error");
    

    EDIT:
    Natürlich kann man auch noch prüfen, ob es sich wirklich um einen Pointer handelt:

    // zur Laufzeit:
    
    template<class Signature>
    struct FunctionTypeExtractor;
    
    template<typename R, typename P0>
    struct FunctionTypeExtractor<R (__cdecl*)(P0)> 
    {
    	enum {ParamSize = sizeof(P0)};
    };
    
    template<class Class>
    class MyClass
    {
    public:
    	template<class Signature>
    	MyClass(Class* Obj, Signature DelFunc) : obj(Obj) 
    	{
    		if( FunctionTypeExtractor<Signature>::ParamSize == sizeof(void*) )
    		{
    			this->Delete = (void (__cdecl*)(Class*))DelFunc;
    		}
    		else
    			throw std::exception("error"); 
    	}	
    };
    
    // Zur Kompilierzeit:
    
    template<unsigned n>
    class SIZE_OF_POINTER;
    
    template<>
    class SIZE_OF_POINTER<sizeof(void*)> {};
    
    template<class Signature>
    struct FunctionTypeExtractor;
    
    template<typename R, typename P0>
    struct FunctionTypeExtractor<R (__cdecl*)(P0)> 
    {
    	SIZE_OF_POINTER<sizeof(P0)> dummy;
    };
    
    template<class Class>
    class MyClass
    {
    public:
    	template<class Signature>
    	MyClass(Class* Obj, Signature DelFunc) : obj(Obj) 
    	{
    		FunctionTypeExtractor<Signature> dummy;
    		this->Delete = (void (__cdecl*)(Class*))DelFunc;
    	}	
    };
    

  • Administrator

    @FrEEzE2046,
    Und du bist immer noch der Meinung, dass du keine "Funktor-Klasse" benötigst? Du zerstörst die Typsicherheit und probierst sie dann irgendwie halb über Templatemataprogrammierung wieder einzuführen. Sogar teilweise über Laufzeitfehler statt Kompilerfehler und ich hoffe doch, dass du mit mir übereinstimmst, dass Kompilerfehler viel besser sind als Laufzeitfehler. All das zeigt doch schon, dass da etwas nicht stimmt.

    Zudem auch mit deinen bisherigen Sicherheiten, kann man immer noch Unsinn bauen:

    void foo(int) {}
    void bar(char*) {}
    
    // ...
    
    MyClass<int> a(new int, &foo);
    MyClass<int> b(new int, &bar);
    

    Unsinn bedeutet hier, dass man versehentlich falsche Funktion übergeben könnte. Durch einen Schreibfehler, eine Unachtsamkeit oder sonstwas. Was das am Ende heissen wird, ist völlig frei. Womöglich löschst du tatsächlich die Datenbank mit so einem Fehler, weil durch den Schreibfehler eine Funktion zum löschen der Datenbank aufgerufen wurde und zufälligerweise der richtige Parameter übergeben wurde.

    Und nicht zuletzt zeigt auch dein Kompilerfehler-Problem, wegen welchem du diesen Thread eröffnet hast, dass dein Cast irgendwelchen Unsinn produziert. Als Templateparameter kann nämlich eine überladene Funktion aktzeptiert werden, nur kann man sie halt nicht in einem solchen Funktionszeiger ablegen. Der Kompiler ist dann anscheinend nicht mal mehr in der Lage eine sinnvolle Fehlermeldung zu generieren, bei einem solch dreckigen Cast 😉

    Was man auch nicht vergessen sollte, bzw. was auch noch ein grosser Vorteil von z.B. boost::function , bzw. std::tr1::function , ist, dass man auch Funktoren übergeben kann. Man hat eine deutlich grössere Freiheit und trotzdem volle Typsicherheit.

    Braucht es noch mehr, um dich von diesem falschen Weg abzubringen? Oder willst du wirklich lieber ins Verderben rennen? 😃

    Grüssli



  • Danke für deine Antwort, wegen Kompiler- o. Laufzeitfehler brauchen wir uns nicht zu unterhalten, aber ansonsten muss ich dir weiterhin widersprechen.

    Eine falsche Funktion, die in Wirklichkeit etwas ganz anderes löscht oder tut, kann ich auch einer Functor-Klasse übergeben. Wo siehst du da den Schutz? Verschreiben kann ich mich überall.

    Zweitens spielt es auch keine Rolle, ob eine überladene Funktion übergeben wird.
    1. Beschwert sich der Kompiler dann über ein doppeldeutiges Symbol.
    2. Möchte ich aber unbedingt eine überladene Funktion übergeben, kann ich ja wieder casten:

    void func(int*) {}
    void func(int*, char) {}
    
    MyClass<int> myc5(new int, (void (*)(int*))func);
    

    Ich denke, dass auf diese Art die Typsicherheit gewährleistet ist.

    EDIT:
    ganz nebenbei kann auch std::tr1::function nicht zwischen überladenen Funktionen unterscheiden:

    // Error	1	error C2664: 'std::tr1::function<_Fty>::function(const std::tr1::function<_Fty> &)' : cannot convert parameter 1 from 'overloaded-function' to 'const std::tr1::function<_Fty> &'
    std::tr1::function<void (int*)> myfunc(func);
    

  • Administrator

    FrEEzE2046 schrieb:

    Eine falsche Funktion, die in Wirklichkeit etwas ganz anderes löscht oder tut, kann ich auch einer Functor-Klasse übergeben. Wo siehst du da den Schutz? Verschreiben kann ich mich überall.

    Es geht nicht um einen absoluten Schutz, sondern einer Verminderung der Fehlerquote. Wenn man MyClass<int> erstellt, dann sollte man auch nur Funktionen übergeben können, welche einen int* oder void* erwartet. Bei deiner Version kann man aber halt auch ein char* übergeben, oder sonst irgendeinen Zeiger. Das ist mit boost::function oder std::tr1::function nicht möglich und es ist somit eine Reduzierung der Fehlerwahrscheinlichkeit vorhanden.
    Und das Geile am Ganzen ist, dass du noch zusätzlich weitere Verwendungsmöglichkeiten bekommst.

    Und nun die Frage an dich: Wo siehst du denn bitte die Nachteile, dass du dich so dagegen sträubst und es lieber über wilde und unsaubere Casts machen möchtest?

    FrEEzE2046 schrieb:

    2. Möchte ich aber unbedingt eine überladene Funktion übergeben, kann ich ja wieder casten:

    void func(int*) {}
    void func(int*, char) {}
    
    MyClass<int> myc5(new int, (void (*)(int*))func);
    

    Ich wage sehr stark zu bezweifeln, dass dies gültiges ISO C++ ist. Der Cast darf man sicher machen, ob es allerdings garantiert ist, dass dann die richtige Funktion aufgerufen wird, wage ich wirklich stark zu bezweifeln. Kompilerfehler löst man nicht einfach durch Casts, erst recht nicht durch C Cast. Damit handelst du dir nur Fehler ein.

    FrEEzE2046 schrieb:

    Ich denke, dass auf diese Art die Typsicherheit gewährleistet ist.

    Wie ich schon mehrmals aufgezeigt habe, ist sie das nicht.

    FrEEzE2046 schrieb:

    ganz nebenbei kann auch std::tr1::function nicht zwischen überladenen Funktionen unterscheiden:

    Habe ich schon weiter vorne geschrieben. Weshalb ich ja auch empfohlen habe, lieber ein Standardverhalten einzuführen und einfach delete aufzurufen.

    Die Frage steht oben, aber ich schreibe sie vielleicht besser nochmals hin:
    Wieso willst du nicht std::tr1::function verwenden? Was ist dein Beweggrund?

    Grüssli


Anmelden zum Antworten