Was passiert genau, wenn ich einen Point mit NULL initialisiere?


  • Mod

    Paul Müller schrieb:

    Außerdem dreht sich die Frage nicht darum, ob ein Objekt anstatt dem reinen Code mehr Laufzeit kostet, sondern ob ein Objekt zusätzlich zum Code Laufzeit kostet?

    Und das tut es nicht. 0. Niemals.

    Und die eigentliche Frage war ja, ob ein blanker Pointer schneller oder langsamer ist als ein sicher gekapselter Autopointer. Ich wollte auch eigentlich einen simplen Autopointer als Beispiel bringen, aber das wurde dann zu lang, weil ich für sauberen Code auch noch Kopierkonstruktor & Co hätte implementieren müssen und für Demonstrationszwecke diese auch noch benutzen.

    Gut hast du also ein Beispiel gefunden, welches die Kriteren von Shade Of Mine erfüllt.

    Auch beliebige andere Objekte. Code der das gleiche macht, braucht immer gleich lang, egal ob als Klasse, Template oder sonstwas.

    SeppJ schrieb:

    Die Sache, die Paul Müller wohl nicht ganz verstanden hat, ist, dass es da gar keinen Funktionsaufruf gibt.

    Ich weiß was inline macht.

    Da ist nichts zu inlinen. Da ist gar kein Funktionsaufruf. Zeig mir den geinlinten Konstruktoraufruf im Maschinencode, wenn da einer ist.

    Du gehst aber die ganze Zeit davon aus, dass die Klasse exakt den selben Code ausführt, den du auch so verwendet hättest. Und das ist nicht immer der Fall.

    Wieso nicht?

    Nebenbei unterschlägst du auch, das inlining nicht immer möglich ist. Im Falle eines ctors/dtors zwar eigentlich schon, aber ein Objekt hat ja auch noch Methoden.

    Wieso nicht?

    Du hast es ja eigentlich sehr einfach, du brauchst bloß ein Gegenbeispiel zu zeigen, bei dem eine (natürlich nicht absichtlich schlecht programmierte) Klasse auch nur einen Maschinenbefehl mehr benötigt als ein vergleichbarer Code ohne Klasse. Dein Zug.

    edit: Ok, hier ein Sparpointer:

    int main()
    {
      int *data=new int;
      cin>>*data;
      cout<<*data<<'\n';
      delete data;
      data=0; // Optional, aber du willst es ja so. Wird hier ohnehin wegoptimiert
    }
    

    Gegen:

    class heap_int
    {
    private:
      int *data;
      heap_int operator=(heap_int){return *this;}
      heap_int(const heap_int&){}
    public:
      heap_int():data(new int){}
      ~heap_int(){delete data;}
      int& operator*(){return *data;}
    };
    
    int main()
    {
      heap_int data;
      cin>>*data;
      cout<<*data<<'\n';
    }
    

    Vorteil der Klasse: Nachdem die Entwicklungsarbeit für die Klasse erst einmal geleistet wurde (wurde sie ja bereits in der Standardbibliothek), ist der Code Anwendungscode kürzer und sicherer. Ich kann das new und das delete gar nicht vergessen und auch gar nicht doppelt aufrufen.

    Resultierender Maschinencode ist wieder (für alle praktischen Zwecke) identisch:

    pushq	%rbx
    	.cfi_def_cfa_offset 16
    	movl	$4, %edi
    	subq	$16, %rsp
    	.cfi_def_cfa_offset 32
    	.cfi_offset 3, -16
    	call	_Znwm
    	movl	$_ZSt3cin, %edi
    	movq	%rax, %rbx
    	movq	%rax, %rsi
    	call	_ZNSirsERi
    	movl	(%rbx), %esi
    	movl	$_ZSt4cout, %edi
    	call	_ZNSolsEi
    	leaq	15(%rsp), %rsi
    	movl	$1, %edx
    	movq	%rax, %rdi
    	movb	$10, 15(%rsp)
    	call	_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
    	movq	%rbx, %rdi
    	call	_ZdlPv
    	xorl	%eax, %eax
    	addq	$16, %rsp
    	popq	%rbx
    	ret
    
    pushq	%rbx
    	.cfi_def_cfa_offset 16
    	movl	$4, %edi
    	subq	$32, %rsp
    	.cfi_def_cfa_offset 48
    .LEHB0:
    	.cfi_offset 3, -16
    	call	_Znwm
    .LEHE0:
    	movl	$_ZSt3cin, %edi
    	movq	%rax, %rsi
    	movq	%rax, %rbx
    .LEHB1:
    	call	_ZNSirsERi
    	movl	(%rbx), %esi
    	movl	$_ZSt4cout, %edi
    	call	_ZNSolsEi
    	leaq	31(%rsp), %rsi
    	movl	$1, %edx
    	movq	%rax, %rdi
    	movb	$10, 31(%rsp)
    	call	_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
    .LEHE1:
    	movq	%rbx, %rdi
    	call	_ZdlPv
    	xorl	%eax, %eax
    	addq	$32, %rsp
    	popq	%rbx
    	ret
    

    An den Kommentaren die eingefügt wurden sieht man den wahren Unterschied: Der Compiler hat im zweiten Fall mehr gearbeitet. Ja, hier gibt es einen Unterschied. Dies ist der Preis der Sicherheit: Compilezeit.



  • SeppJ schrieb:

    Du hast es ja eigentlich sehr einfach, du brauchst bloß ein Gegenbeispiel zu zeigen, bei dem eine (natürlich nicht absichtlich schlecht programmierte) Klasse auch nur einen Maschinenbefehl mehr benötigt als ein vergleichbarer Code ohne Klasse. Dein Zug.

    Ich spar mir jetzt mal das Assembly, weil der Diff doch etwas länger ist.

    int main()
    {
        int * foo(new int);
        int * bar(foo);
        delete bar;
        return 0;
    }
    
    #include <tr1/memory>
    
    int main()
    {
        std::tr1::shared_ptr<int> foo(new int);
        std::tr1::shared_ptr<int> bar(foo);
        return 0;
    }
    


  • Die Code sind meiner Meinung nach nicht miteinander vergleichbar!



  • Das ist jetzt aber ein Vergleich von Äpfeln mit.. (nein Birnen wären noch zu nahe dran..) UFO's.

    /EDIT
    Wenn man für dein gewünschtes Verhalten einen solchen Smart Pointer nimmt, dann hat man auch nichts anderes als schlechte Performance verdient.

    Vergleichbar wäre so etwas:

    class my_wrapper
    {
    public:
     my_wrapper(int* p)
     : p_(p){}
    
     my_wrapper(my_wrapper const& other)
     : p_(other.p_){}
    
     void destroy()
     {
       delete p_;
     }
    private:
     int* p_;
    };
    
    int main() 
    { 
        my_wrapper foo(new int); 
        my_wrapper bar(foo); 
        bar.destroy();
    
        return 0; 
    }
    

    Gleiche Vorteile, gleiche Nachteile. Das sollte jetzt so ziemlich zu dem asm Code kompiliert werden, wie deine Version.

    Obwohl ich schon auch der Meinung bin, dass Abstraktion kosten kann. Kommt aber auf die Programmierweise und den Compiler an. Dass man sich über solche Sachen keine Gedanken machen muss, wenn man High Level programmiert ist ja selbstverständlich.


  • Mod

    Ich habe oben noch ein weiteres Beispiel, wo die Codes auch tatsächlich das gleiche machen, reineditiert. Das dein Vergleich hinkt, muss ich dir wohl nicht noch erklären. Ein shared_pointer macht ganz was anderes als ein roher Pointer. Wenn du die Referenzenzähler in deinem Vergleichscode auch noch einbaust und benutzt, dann kann man sie vergleichen. Vorher macht der zweite Code einfach mehr.



  • Warum soll das ein Vergleich von Äpfeln mit UFOs sein? Der Code macht das gleiche und darum geht es. Es war nicht gefragt die Aussage von SeppJ zu bekräftigen, indem ich eine Klasse suche, die genauso wenig Code wie ein roher Zeiger erzeugt.



  • Der Code von Drakon ist doch nicht mehr als ein schlechter Scherz, welchen Sinn versprichst du dir von deinem "Wrapper"? Also ein wenig Ernsthaftigkeit sollte schon dabei. Auch wenn ich es mit einem Extrembeispiel etwas auf die Spitze getrieben habe.



  • Paul Müller schrieb:

    Der Code von Drakon ist doch nicht mehr als ein schlechter Scherz, welchen Sinn versprichst du dir von deinem "Wrapper"? Also ein wenig Ernsthaftigkeit sollte schon dabei. Auch wenn ich es mit einem Extrembeispiel etwas auf die Spitze getrieben habe.

    Das ist nicht mehr als dein Code auch macht. Wenn du mehr/anderes Verhalten haben willst, dann wird natürlich auch mehr Code erzeugt. Dass das so nicht viel Sinn macht ist ja wohl klar, aber es ist Abstraktion, die (bei einem guten Compiler) nichts kostet.

    Warum soll das ein Vergleich von Äpfeln mit UFOs sein? Der Code macht das gleiche und darum geht es. Es war nicht gefragt die Aussage von SeppJ zu bekräftigen, indem ich eine Klasse suche, die genauso wenig Code wie ein roher Zeiger erzeugt.

    Er macht nicht ganz das gleiche, nein. Der Smart Pointer weiss was alles referenziert wird und man muss sich da nicht selbst darum kümmern.
    Wenn du den Teil aus dem Smart Pointer rausnimmst wird das Beispiel genau gleich schnell sein. Das Beispiel von SeppJ oben illustriert das auch recht gut.



  • Paul Müller schrieb:

    ...Also ein wenig Ernsthaftigkeit sollte schon dabei...

    Dann fasse dir bitte auch mal selbst an die Nase. Ein scoped_ptr oder auto_ptr käme ja wenigstens noch in die Funktionelle Nähe eines reinen Zeigers. Ein shared_ptr erfüllt aber wesentlich mehr als nur einen Zeiger zu wrappen, und ist dann sinnvoll wenn eine Referenzzählung nötig ist, weil nicht sicher gestellt werden kann welcher der verwendeten Stellen, die letzte ist.

    Wenn muss man wirklich auch gleiches mit gleichen vergleichen. Und dann kostet OO und Templates nicht viel, und bringen zudem richtig eingesetzt auch zusätzliche Sicherheit und Wartbarkeit mit fast 0 Mehrkosten.

    Nachtrag: Und wenn wir schon Äpfel mit Birnen vergleichen, kann ich ja auch gleich das Gegenbeispiel bringen. Ein Programmierer meinte mir gegenüber immer das die Standardbibliothek etc. soviel mehr kosten, und baute sich daher eine auf seine Fälle "optimierte" Fassung zurecht, die fast ausschließlich prozedural geschrieben war. Als ich irgendwann diesen Code benutzen musste, und einen Fehler darin fand, tauschte ich das spaßeshalber mal mit einen mehr an "Abstraktion" aus (Und unter Verwendung von Klassen und der Standardbibliothek). Nicht nur das der Code danach ungefähr ein Zehntel so groß (und verständlicher) war, lief er auch noch um ein Drittel schneller.

    Geschwindigkeit ist nicht nur durch einzelne Codestellen, sondern auch in Kombination zu sehen. Und wenn gleichzeitig Wartungsaspekte hinzu kommen, habe ich in der Praxis nicht selten erlebt das lesbarer Code nicht selten sogar genauso schnell (oder gar schneller) als die Handoptimierten war.



  • Die Aufgabe war aber nicht Abstraktion so anzupassen, dass sie keinen zusätzlichen Code erzeugt, sondern dass sie sinnvoll ist.
    Und bei deiner Abstraktion geht jeglicher Sinn verloren. Meine macht dahingehend Sinn, dass beide Zeiger gültig sind, so wie im Ursprungsprogramm gefordert. Und ich muss mich nicht selber um das delete oder bei dir destroy kümmern, wofür ich ja eigentlich hier die Abstraktion einsetze. Und zu guter Letzt ich ändere nicht einfach eine bestehende Klasse. Was auch ein Punkt von sinnvoller Abstraktion ist.



  • Paul Müller schrieb:

    Die Aufgabe war aber nicht Abstraktion so anzupassen, dass sie keinen zusätzlichen Code erzeugt, sondern dass sie sinnvoll ist.

    Sinnvoll und vergleichbar, was dem Vergleich reiner Zeiger gegenüber shared_ptr eindeutig widerspricht. Du wirst ja hoffentlich auch nicht für 50m Fußweg ein Auto verwenden.

    Paul Müller schrieb:

    Meine macht dahingehend Sinn, dass beide Zeiger gültig sind, so wie im Ursprungsprogramm gefordert.

    Aber du nimmst absichtlich für den Fall ungeeignete Beispiele.

    Paul Müller schrieb:

    Und zu guter Letzt ich ändere nicht einfach eine bestehende Klasse. Was auch ein Punkt von sinnvoller Abstraktion ist.

    Aber auch bei der Verwendung bestehender Klassen nimmt man die passenden. Wenn eine Schraube leicht locker ist, rufe ich ja auch nicht gleich den Fachmann an, sondern nehme einen Schraubenzieher zur Hand.



  • Deine Abstraktion ist auch nicht sehr sinnvoll. shared_ptr benutzt man nicht, um in einem Scope ein kurzlebiges Objekt zu verwalten, sondern um ein Objekt in multiplen Kontexten verwalten zu können. In dem Beispiel wäre ein scoped_ptr sinnvoll. Das würde auch wieder vergleichbaren ASM-Code erzeugen.



  • Es ging bei meinen Beispiel nur darum die Aussage von SeppJ zu widerlegen, dass die Nutzung von Klassen niemals Bloat nach sich zieht. Da hab einfach das erstbeste genommen, wo ich mir sicher war. Die extreme sehe ich eher als Unterstützung meiner Aussage. Dass der Code absolut keinen Sinn ergibt ist dabei nicht von Bedeutung.



  • Wenn Du für Deine Zeiger manuell eine Referenzzählung durchführen würdest, dann würde im Vergleich kein „bloat“ entstehen. Funktional wäre es dann auch vergleichbar. So ist Dein Beispiel einfach Nonsense.



  • Tachyon schrieb:

    Deine Abstraktion ist auch nicht sehr sinnvoll.

    Das war auch nicht gefordert. Es wurde nur nach einem Beispiel verlangt.

    Tachyon schrieb:

    In dem Beispiel wäre ein scoped_ptr sinnvoll.

    Nein wäre er nicht, weil er nicht dupliziert werden kann. Ich will über den Sinn nicht streiten, der Sinn ist ein äquivalentes Code Verhalten. Es geht hier auch nicht um den shared_ptr, sondern um eine Klasse die bloat erzeugt und da ist der shared_ptr nunmal ein gutes Beispiel. Und auch ein praxisrelevantes Objekt.



  • Tachyon schrieb:

    Wenn Du für Deine Zeiger manuell eine Referenzzählung durchführen würdest, dann würde im Vergleich kein „bloat“ entstehen. Funktional wäre es dann auch vergleichbar. So ist Dein Beispiel einfach Nonsense.

    Nein es wäre funktional nicht vergleichbar. Weil keine Referenzzählung vorgesehen ist. Die macht nur der shared_ptr, damit ich den Luxus hab kein delete aufrufen zu müssen. Wär mir dieser Luxus egal und das ist er mir persönlich, dann würde ich auch keine Verwendung für eine Abstraktion haben.



  • Paul Müller schrieb:

    Tachyon schrieb:

    Deine Abstraktion ist auch nicht sehr sinnvoll.

    Das war auch nicht gefordert. Es wurde nur nach einem Beispiel verlangt.

    Paul Müller schrieb:

    Die Aufgabe war aber nicht Abstraktion so anzupassen, dass sie keinen zusätzlichen Code erzeugt, sondern dass sie sinnvoll ist.

    Was denn nun?



  • Paul Müller schrieb:

    Tachyon schrieb:

    Deine Abstraktion ist auch nicht sehr sinnvoll.

    Das war auch nicht gefordert. Es wurde nur nach einem Beispiel verlangt.

    Tachyon schrieb:

    In dem Beispiel wäre ein scoped_ptr sinnvoll.

    Nein wäre er nicht, weil er nicht dupliziert werden kann. Ich will über den Sinn nicht streiten, der Sinn ist ein äquivalentes Code Verhalten. Es geht hier auch nicht um den shared_ptr, sondern um eine Klasse die bloat erzeugt und da ist der shared_ptr nunmal ein gutes Beispiel. Und auch ein praxisrelevantes Objekt.

    Das ist es doch, deine beide Beispiele haben nicht das gleiche äquivalentes Code Verhalten.

    Oder muss ich deutlicher werden:
    Das Verhalten deines Programm ist das Verhalten, welches du selbst geschrieben hast, und das Verhalten, dass du nutzt.



  • Es wurde ganz deutlich nach einem Beispiel gefragt, welches beweisen soll, dass ctor-/dtor-Aufrufe Performanceeinbußen generieren. Du hast bisher keins vorgelegt, dass deine These glaubhaft unterstützt.



  • Tachyon schrieb:

    Was denn nun?

    In Bezug auf die Verwendung eines Wrappers. Ob das Beispiel Sinn macht ist doch egal.

    Zeus schrieb:

    Das ist es doch, deine beide Beispiele haben nicht das gleiche äquivalentes Code Verhalten.

    Wo liegt der Unterschied?


Anmelden zum Antworten