Anfängerfrage wegen Klasse



  • camper schrieb:

    Skym0sh0 schrieb:

    Woher kommt/kam dieser Mythos eigentlich, dass man vorm deleten/freen erstmal einen Nullcheck macht?

    Man kann dafür Argumente finde, die haben allerdings absolut nichts mit Sicherheit zu tun
    - unter bestimmten Umständen kann es zu besserem Code führen (die Aussage, dass delete auf 0 keine Effekt hat bedeutete vor C++11 nicht, dass der Compiler den Aufruf der Deallokationsfunktion einfach eleminieren durfte)

    Unter Umständen kann ein Schwein auch fliegen.
    Du müsstest diese Aussage erst einmal belegen und mir dann noch erklären warum Programmierer, die von sonst nichts einen Plan haben, ausgerechnet bei delete zu Mikrooptimierungsgenies werden sollen.

    camper schrieb:

    - delete auf 0 hebt die Paarigkeit zwischen new und delete gerade auf, und das Argument, dass delete damit zurecht kommt, kann man auch umdrehen: warum überhaupt einen sinnlosen Funktionsaufruf durchführen?

    Es gibt keine Paarigkeit zwischen new und delete . new wurde wahrscheinlich vor den Referenzen eingeführt, also gibt es bis heute einen Zeiger zurück. Das gleiche gilt im Prinzip für delete . Weil delete einen Zeiger nehmen musste, konnte man auch gleich die Semantik von free übernehmen, auch um C-Programmierern den Umstieg leichter zu machen. free kommt aus zwei Gründen mit Nullzeigern klar:
    1. Es kann den Clean-Up-Teil von Funktionen vereinfachen.
    2. Es spart dabei Verzweigungsinstruktionen und damit Speicher.

    Mal abgesehen davon, dass jedes Vorkommen vom delete -Schlüsselwort ein Bug ist, gibt es ganz banale Argumente gegen das redundante if :
    - es ist mehr Schreibarbeit
    - es ist mehr Lesearbeit
    - es ist hässlich
    - es ist nicht schneller

    Der typische Fall ist doch, dass der Zeiger nicht null ist. Warum also für den Fall optimieren, dass der null ist? Man verwirrt den Prozessor mit der Verzweigung nur. Ein Aufruf einer konstanten Funktion wie delete ist auch gar nicht teuer. Die delete -Funktion wird in den meisten Programme ohnehin sehr häufig aufgerufen, also spart man wahrscheinlich nicht einmal Instruction Cache, wenn man die Aufrufe meidet. Moderne Prozessoren würden vielleicht sogar spekulativ das if und das zugehörige delete gleichzeitig ausführen. Dann hätte man nur Zeit verschwendet. Außerdem weiß man beim Programmieren in C++ gar nicht mit welchem Prozessor man es mal zu tun haben könnte. Es kann gut sein, dass das if auf dem einen Prozessor minimal Zeit spart, aber auf dem anderen die Ausführung stark verlangsamt. Die Unterschiede bei so etwas sind zwischen Prozessormodellen oft riesig, zum Beispiel Atom vs i7. Die sind beide von Intel und fressen denselben Code, unterscheiden sich aber stark bei internen Optimierungen wie out of order execution, was hier eine Rolle spielen kann.
    Kurz: Das mit der Optimierung ist Unsinn.

    Es kann natürlich sein, dass Profiling auf einer Hardware dem if in einem konkreten Programm einen Vorteil bescheinigt. Dann kann man so etwas machen:

    #include <memory>
    
    template <class T>
    struct optimized_deleter
    {
    	void operator ()(T *p) const
    	{
    #ifdef PROCESSOR_ARCHITECTURE_XY
    		//benchmark 53 shows that this is a bit faster in most cases ...
    		if (!p)
    		{
    			return;
    		}
    #endif
    		delete p;
    	}
    };
    
    int main()
    {
    	std::unique_ptr<int, optimized_deleter<int>> p(new int(1));
    	auto q = std::move(p);
    }
    


  • TyRoXx schrieb:

    camper schrieb:

    Skym0sh0 schrieb:

    Woher kommt/kam dieser Mythos eigentlich, dass man vorm deleten/freen erstmal einen Nullcheck macht?

    Man kann dafür Argumente finde, die haben allerdings absolut nichts mit Sicherheit zu tun
    - unter bestimmten Umständen kann es zu besserem Code führen (die Aussage, dass delete auf 0 keine Effekt hat bedeutete vor C++11 nicht, dass der Compiler den Aufruf der Deallokationsfunktion einfach eleminieren durfte)

    Unter Umständen kann ein Schwein auch fliegen.
    Du müsstest diese Aussage erst einmal belegen und mir dann noch erklären warum Programmierer, die von sonst nichts einen Plan haben, ausgerechnet bei delete zu Mikrooptimierungsgenies werden sollen.

    camper schrieb:

    - delete auf 0 hebt die Paarigkeit zwischen new und delete gerade auf, und das Argument, dass delete damit zurecht kommt, kann man auch umdrehen: warum überhaupt einen sinnlosen Funktionsaufruf durchführen?

    Es gibt keine Paarigkeit zwischen new und delete . new wurde wahrscheinlich vor den Referenzen eingeführt, also gibt es bis heute einen Zeiger zurück. Das gleiche gilt im Prinzip für delete . Weil delete einen Zeiger nehmen musste, konnte man auch gleich die Semantik von free übernehmen, auch um C-Programmierern den Umstieg leichter zu machen. free kommt aus zwei Gründen mit Nullzeigern klar:
    1. Es kann den Clean-Up-Teil von Funktionen vereinfachen.
    2. Es spart dabei Verzweigungsinstruktionen und damit Speicher.

    Mal abgesehen davon, dass jedes Vorkommen vom delete -Schlüsselwort ein Bug ist, gibt es ganz banale Argumente gegen das redundante if :
    - es ist mehr Schreibarbeit
    - es ist mehr Lesearbeit
    - es ist hässlich
    - es ist nicht schneller

    Der typische Fall ist doch, dass der Zeiger nicht null ist. Warum also für den Fall optimieren, dass der null ist? Man verwirrt den Prozessor mit der Verzweigung nur. Ein Aufruf einer konstanten Funktion wie delete ist auch gar nicht teuer. Die delete -Funktion wird in den meisten Programme ohnehin sehr häufig aufgerufen, also spart man wahrscheinlich nicht einmal Instruction Cache, wenn man die Aufrufe meidet. Moderne Prozessoren würden vielleicht sogar spekulativ das if und das zugehörige delete gleichzeitig ausführen. Dann hätte man nur Zeit verschwendet. Außerdem weiß man beim Programmieren in C++ gar nicht mit welchem Prozessor man es mal zu tun haben könnte. Es kann gut sein, dass das if auf dem einen Prozessor minimal Zeit spart, aber auf dem anderen die Ausführung stark verlangsamt. Die Unterschiede bei so etwas sind zwischen Prozessormodellen oft riesig, zum Beispiel Atom vs i7. Die sind beide von Intel und fressen denselben Code, unterscheiden sich aber stark bei internen Optimierungen wie out of order execution, was hier eine Rolle spielen kann.
    Kurz: Das mit der Optimierung ist Unsinn.

    Nö.


  • Mod

    TyRoXx schrieb:

    camper schrieb:

    Skym0sh0 schrieb:

    Woher kommt/kam dieser Mythos eigentlich, dass man vorm deleten/freen erstmal einen Nullcheck macht?

    Man kann dafür Argumente finde, die haben allerdings absolut nichts mit Sicherheit zu tun
    - unter bestimmten Umständen kann es zu besserem Code führen (die Aussage, dass delete auf 0 keine Effekt hat bedeutete vor C++11 nicht, dass der Compiler den Aufruf der Deallokationsfunktion einfach eleminieren durfte)

    Unter Umständen kann ein Schwein auch fliegen.
    Du müsstest diese Aussage erst einmal belegen und mir dann noch erklären warum Programmierer, die von sonst nichts einen Plan haben, ausgerechnet bei delete zu Mikrooptimierungsgenies werden sollen.

    Als Erstes zeigst du mir bitte, wo ich zu Mikrooptimierung aufgerufen habe. Und wenn du ein zutreffendes Beispiel haben möchtest, kannst du gerne danach fragen.

    TyRoXx schrieb:

    camper schrieb:

    - delete auf 0 hebt die Paarigkeit zwischen new und delete gerade auf, und das Argument, dass delete damit zurecht kommt, kann man auch umdrehen: warum überhaupt einen sinnlosen Funktionsaufruf durchführen?

    Es gibt keine Paarigkeit zwischen new und delete . new wurde wahrscheinlich vor den Referenzen eingeführt, also gibt es bis heute einen Zeiger zurück. Das gleiche gilt im Prinzip für delete . Weil delete einen Zeiger nehmen musste, konnte man auch gleich die Semantik von free übernehmen, auch um C-Programmierern den Umstieg leichter zu machen. free kommt aus zwei Gründen mit Nullzeigern klar:
    1. Es kann den Clean-Up-Teil von Funktionen vereinfachen.
    2. Es spart dabei Verzweigungsinstruktionen und damit Speicher.

    Mal abgesehen davon, dass jedes Vorkommen vom delete -Schlüsselwort ein Bug ist, gibt es ganz banale Argumente gegen das redundante if :
    - es ist mehr Schreibarbeit
    - es ist mehr Lesearbeit
    - es ist hässlich
    - es ist nicht schneller

    Der typische Fall ist doch, dass der Zeiger nicht null ist. Warum also für den Fall optimieren, dass der null ist? Man verwirrt den Prozessor mit der Verzweigung nur. Ein Aufruf einer konstanten Funktion wie delete ist auch gar nicht teuer. Die delete -Funktion wird in den meisten Programme ohnehin sehr häufig aufgerufen, also spart man wahrscheinlich nicht einmal Instruction Cache, wenn man die Aufrufe meidet. Moderne Prozessoren würden vielleicht sogar spekulativ das if und das zugehörige delete gleichzeitig ausführen. Dann hätte man nur Zeit verschwendet. Außerdem weiß man beim Programmieren in C++ gar nicht mit welchem Prozessor man es mal zu tun haben könnte. Es kann gut sein, dass das if auf dem einen Prozessor minimal Zeit spart, aber auf dem anderen die Ausführung stark verlangsamt. Die Unterschiede bei so etwas sind zwischen Prozessormodellen oft riesig, zum Beispiel Atom vs i7. Die sind beide von Intel und fressen denselben Code, unterscheiden sich aber stark bei internen Optimierungen wie out of order execution, was hier eine Rolle spielen kann.
    Kurz: Das mit der Optimierung ist Unsinn.

    Es kann natürlich sein, dass Profiling auf einer Hardware dem if in einem konkreten Programm einen Vorteil bescheinigt. Dann kann man so etwas machen:

    #include <memory>
    
    template <class T>
    struct optimized_deleter
    {
    	void operator ()(T *p) const
    	{
    #ifdef PROCESSOR_ARCHITECTURE_XY
    		//benchmark 53 shows that this is a bit faster in most cases ...
    		if (!p)
    		{
    			return;
    		}
    #endif
    		delete p;
    	}
    };
    
    int main()
    {
    	std::unique_ptr<int, optimized_deleter<int>> p(new int(1));
    	auto q = std::move(p);
    }
    

    Nö.

    Edit: ach nein, das hat volkard ja schon gesagt.


  • Mod

    Moderne Prozessoren würden vielleicht sogar spekulativ das if und das zugehörige delete gleichzeitig ausführen.

    delete resultiert doch in einem API-Aufruf? Oder steckt der Nullpointertest schon im operator delete drin? In letzterem Fall ist das if Überflüssig und wird einfach wegoptimiert (zweimal hintereinander denselben Wert von einer Variable abfragen). Wenn nicht, wird tatsächlich der Funktionsaufruf gespart.


  • Mod

    Könnte man dann nicht folgendes schreiben

    if( __builtin_expect( p != nullptr, 1 ) )
        delete p;
    


  • Arcoth schrieb:

    delete resultiert doch in einem API-Aufruf? Oder steckt der Nullpointertest schon im operator delete drin?

    Könnte so oder anders sein, es wäre nicht beobachtbar. In der Annahme, daß die API 0 nicht haben will, sonbdern einen Fehler zurückgibt, könnte die Lib trotzdem entweder vorher abfragen oder blind dagegenrennen und nachträglich den Fehler CALLED_API_FREE_WITH_NULLPTR wegverschweigen.
    Naja, ich tendiere mal ernsthaft dazu, daß man vorher fragt, spätestens seit unique_ptr. Und daß der Test im delete dem Compiler zu Optimierungszwecken bekanntgemacht wird (zur Not inline-wrapper mit nur dem if drum).



  • Arcoth schrieb:

    Könnte man dann nicht folgendes schreiben

    if( __builtin_expect( p != nullptr, 1 ) )
        delete p;
    

    Kann man schon. Mein GCC 4.8 generiert da aber exakt den gleichen Code. Der geht also standardmäßig davon aus, dass der Zeiger nicht null ist.

    Mal ein bisschen Code (-O3):

    void delete_with_if(int *p)
    {
    	if (p)
    	{
    		delete p;
    	}
    }
    /*
    	testq	%rdi, %rdi
    	je	.L1
    	jmp	_ZdlPv
    .L1:
    	rep ret
    */
    
    void delete_with_expected_if(int *p)
    {
    	if (__builtin_expect(p != nullptr, 1))
    	{
    		delete p;
    	}
    }
    /*
    	testq	%rdi, %rdi
    	je	.L4
    	jmp	_ZdlPv
    .L4:
    	ret
    */
    
    void delete_with_unexpected_if(int *p)
    {
    	if (__builtin_expect(p != nullptr, 0))
    	{
    		delete p;
    	}
    }
    /*
    	testq	%rdi, %rdi
    	jne	.L8
    	rep ret
    .L8:
    	jmp	_ZdlPv
    */
    
    void delete_without_if(int *p)
    {
    	delete p;
    }
    /*
    	jmp	_ZdlPv
    */
    

    Das __builtin_expect mit umgekehrter Erwartung stellt den Sprung wie erwartet um. Die Variante ohne if ist auf jeden Fall die kürzeste.

    Welche Variante besser ist, weiß ich nicht. Aber das wissen die Verteidiger des if auch nicht. Das ist mein Punkt.



  • TyRoXx schrieb:

    Es gibt keine Paarigkeit zwischen new und delete .

    Nur weil "delete 0" sie zerstört.
    Sonst wären die Funktionen ein wunderschönes Make/Break Paar.

    Wenn du was anderes behaupten willst, dann bitte mit mehr Details.



  • hustbaer schrieb:

    TyRoXx schrieb:

    Es gibt keine Paarigkeit zwischen new und delete .

    Nur weil "delete 0" sie zerstört.
    Sonst wären die Funktionen ein wunderschönes Make/Break Paar.

    Ja.
    Alles, was new zurückgibt, kann delete annehmen.
    Aber nicht alles, was delete annehmen kann, kann new zurückgeben.
    delete hat also eine unnötig weite Schnittstelle. Man könnte das ein unsauberes Paar nennen.
    Wenn man die heute entwerfen würde, wären das wohl jeweils Referenzen statt Zeiger, was ein perfektes Paar ergäbe. unique_ptr würde dann die Nullability mit einem Zeiger und if umsetzen.



  • TyRoXx schrieb:

    Wenn man die heute entwerfen würde, wären das wohl jeweils Referenzen statt Zeiger, was ein perfektes Paar ergäbe.

    New eine Referenz zurückgeben zu lassen macht überhaupt keinen Sinn. Denk nochmal drüber nach, vielleicht fällt dir dann auf wieso.


Anmelden zum Antworten