Deleter shared_ptr, lambda expression, member variable



  • Hallo ihr zwei,

    Arcoth schrieb:

    Der nächste Fehler, der kein Folgefehler des ersten ist, ist der hier:

    DCMT_wrapper_omp.cpp:15:68: error: cannot convert ‘mt_struct**’ to ‘mt_struct*’ for argument ‘1’ to ‘void free_mt_struct(mt_struct*)’

    Den meinte ich. 😃

    Edit:
    Oh man, war ein Tippfehler bei der Funktion, die im Deleter aufgerufen wird. 😡

    Habe es im Eingangspost editiert und das kompiliert zumindest.

    Gruß,
    -- Klaus.



  • Hallo ihr Lieben,

    ich habe dazu mal wieder eine Frage: Warum kann ich aus dem shared_ptr in Beitrag 1 nicht einfach einen unique_ptr machen und den Rest in der Source File belassen? 😕

    #ifndef DCMT_WRAPPER_OMP_H_
    #define DCMT_WRAPPER_OMP_H_
    #include <iostream>
    #include <memory>
    
    extern "C"
    {
    	#include "dcmt0.6.1/include/dc.h"
    }
    
    struct DCMT_wrapper_omp
    {
    	DCMT_wrapper_omp(unsigned int);
    	int count;
    	int const seed, w, p;
    	double const max_int;
    //	std::shared_ptr<mt_struct*> dcmt;
        std::unique_ptr<mt_struct*,void(*)(mt_struct**)> dcmt;
    };
    #endif
    

    Die Fehlermeldung

    DCMT_wrapper_omp.cpp: In constructor ‘DCMT_wrapper_omp::DCMT_wrapper_omp(unsigned int)’:
    DCMT_wrapper_omp.cpp:9:68: error: no matching function for call to ‘std::unique_ptr<mt_struct*, void (*)(mt_struct**)>::unique_ptr(mt_struct**, DCMT_wrapper_omp::DCMT_wrapper_omp(unsigned int)::<lambda(mt_struct**)>)’
    DCMT_wrapper_omp.cpp:9:68: note: candidates are:
    In file included from /usr/include/c++/4.7/memory:86:0,
                     from DCMT_wrapper_omp.h:10,
                     from DCMT_wrapper_omp.cpp:3:
    /usr/include/c++/4.7/bits/unique_ptr.h:164:2: note: template<class _Up, class> std::unique_ptr::unique_ptr(std::auto_ptr<_Up>&&)
    /usr/include/c++/4.7/bits/unique_ptr.h:164:2: note:   template argument deduction/substitution failed:
    DCMT_wrapper_omp.cpp:9:68: note:   mismatched types ‘std::auto_ptr<_Up>’ and ‘mt_struct**’
    In file included from /usr/include/c++/4.7/memory:86:0,
                     from DCMT_wrapper_omp.h:10,
                     from DCMT_wrapper_omp.cpp:3:
    /usr/include/c++/4.7/bits/unique_ptr.h:155:2: note: template<class _Up, class _Ep, class> std::unique_ptr::unique_ptr(std::unique_ptr<_Up, _Ep>&&)
    /usr/include/c++/4.7/bits/unique_ptr.h:155:2: note:   template argument deduction/substitution failed:
    DCMT_wrapper_omp.cpp:9:68: note:   mismatched types ‘std::unique_ptr<_Up, _Ep>’ and ‘mt_struct**’
    In file included from /usr/include/c++/4.7/memory:86:0,
                     from DCMT_wrapper_omp.h:10,
                     from DCMT_wrapper_omp.cpp:3:
    /usr/include/c++/4.7/bits/unique_ptr.h:142:7: note: std::unique_ptr<_Tp, _Dp>::unique_ptr(std::unique_ptr<_Tp, _Dp>&&) [with _Tp = mt_struct*; _Dp = void (*)(mt_struct**); std::unique_ptr<_Tp, _Dp> = std::unique_ptr<mt_struct*, void (*)(mt_struct**)>]
    /usr/include/c++/4.7/bits/unique_ptr.h:142:7: note:   candidate expects 1 argument, 2 provided
    /usr/include/c++/4.7/bits/unique_ptr.h:136:17: note: constexpr std::unique_ptr<_Tp, _Dp>::unique_ptr(std::nullptr_t) [with _Tp = mt_struct*; _Dp = void (*)(mt_struct**); std::nullptr_t = std::nullptr_t]
    /usr/include/c++/4.7/bits/unique_ptr.h:136:17: note:   candidate expects 1 argument, 2 provided
    /usr/include/c++/4.7/bits/unique_ptr.h:130:7: note: std::unique_ptr<_Tp, _Dp>::unique_ptr(std::unique_ptr<_Tp, _Dp>::pointer, typename std::remove_reference<_To>::type&&) [with _Tp = mt_struct*; _Dp = void (*)(mt_struct**); std::unique_ptr<_Tp, _Dp>::pointer = mt_struct**; typename std::remove_reference<_To>::type = void (*)(mt_struct**)]
    /usr/include/c++/4.7/bits/unique_ptr.h:130:7: note:   no known conversion for argument 2 from ‘DCMT_wrapper_omp::DCMT_wrapper_omp(unsigned int)::<lambda(mt_struct**)>’ to ‘void (*&&)(mt_struct**)’
    /usr/include/c++/4.7/bits/unique_ptr.h:125:7: note: std::unique_ptr<_Tp, _Dp>::unique_ptr(std::unique_ptr<_Tp, _Dp>::pointer, typename std::conditional<std::is_reference<_Dp>::value, _Dp, const _Dp&>::type) [with _Tp = mt_struct*; _Dp = void (*)(mt_struct**); std::unique_ptr<_Tp, _Dp>::pointer = mt_struct**; typename std::conditional<std::is_reference<_Dp>::value, _Dp, const _Dp&>::type = void (* const&)(mt_struct**)]
    /usr/include/c++/4.7/bits/unique_ptr.h:125:7: note:   no known conversion for argument 2 from ‘DCMT_wrapper_omp::DCMT_wrapper_omp(unsigned int)::<lambda(mt_struct**)>’ to ‘std::conditional<false, void (*)(mt_struct**), void (* const&)(mt_struct**)>::type {aka void (* const&)(mt_struct**)}’
    /usr/include/c++/4.7/bits/unique_ptr.h:120:7: note: std::unique_ptr<_Tp, _Dp>::unique_ptr(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = mt_struct*; _Dp = void (*)(mt_struct**); std::unique_ptr<_Tp, _Dp>::pointer = mt_struct**]
    /usr/include/c++/4.7/bits/unique_ptr.h:120:7: note:   candidate expects 1 argument, 2 provided
    /usr/include/c++/4.7/bits/unique_ptr.h:114:17: note: constexpr std::unique_ptr<_Tp, _Dp>::unique_ptr() [with _Tp = mt_struct*; _Dp = void (*)(mt_struct**)]
    /usr/include/c++/4.7/bits/unique_ptr.h:114:17: note:   candidate expects 0 arguments, 2 provided
    make: *** [DCMT_wrapper_omp.o] Fehler 1
    


  • std::shared_ptr<mt_struct*> dcmt;
    

    Warum moechtest du einen shared_ptr von einem Pointer?



  • Das hatten wir hier diskutiert, weil es dieses C Konstrukt eben vorsieht. Ich verstehe das so, dass es einen Array von Arrays anlegt, sprich einen Pointer auf Pointer.

    Gruß,
    -- Klaus.



  • Au backe, 4 Monate ... . Aber vielleicht solltest du mit etwas einfacherem anfangen, ich empfehle meist das Buch: Die C++ Programmiersprache.

    Tip am Rande:

    max_int(pow(2,w)-1)
    
    max_int((1 << w)-1)
    

    Zu deiner Frage: Liesst du die Fehlermeldungen ueberhaupt?

    DCMT_wrapper_omp.cpp:9:68: error: ...

    Die Fehlermeldung nuetzt uns nix, wenn wir den Code dazu nicht sehen. Ich bezweifle, dass er nach 4 Monaten der gleiche ist. Angenommen es ist so, dann kann

    [this](mt_struct** mt){ free_mt_struct_array(mt, this->count); }
    

    nicht in einen Funktionszeiger konvertiert werden, also der Typ der als Deleter angegeben ist.



  • knivil schrieb:

    Zu deiner Frage: Liesst du die Fehlermeldungen ueberhaupt?

    DCMT_wrapper_omp.cpp:9:68: error: ...

    Die Fehlermeldung nuetzt uns nix, wenn wir den Code dazu nicht sehen.

    Zu deiner Antwort:
    Ich lese Fehlermeldungen scheinbar genau so wie du meine Beiträge. 😉

    Klaus82 schrieb:

    ich habe dazu mal wieder eine Frage: Warum kann ich aus dem shared_ptr in Beitrag 1 nicht einfach einen unique_ptr machen und den Rest in der Source File belassen? 😕

    Jetzt sogar hervorgehoben und direkt mit Link zum Startpost. 🙂

    Gruß,
    -- Klaus.



  • Und trotzdem war ich ne Minute schneller. Und nochmal Klartext: Ein lambda-Ausdruck mit Capture kann nicht in einen Funktionszeiger konvertiert werden.



  • knivil schrieb:

    Und trotzdem war ich ne Minute schneller. Und nochmal Klartext: Ein lambda-Ausdruck mit Capture kann nicht in einen Funktionszeiger konvertiert werden.

    Okay,
    also in der Beschreibung von shared_ptr steht für den deleter

    This shall be a callable object taking a pointer to T as argument for its functional call (where T is shared_ptr's template parameter).

    Und genau das erfüllt eben die lambda Funktion!?

    Jetzt sind wir aber beim unique_ptr wo für den deleter steht:

    Deleter object to be used to release the owned object.
    D refers to unique_ptr's second template parameter, which describes its deleter type.

    Ich bin eben etwas verwundert, weil hier auch einfach ein deleter mittels lambda Funktion gebastelt wird.

    Gruß,
    -- Klaus.



  • knivil hat ja auch nicht geschrieben dass das mit einem Lambda-Ausdruck allgemein nicht geht.
    Er hat Lambda-Ausdruck mit Capture geschreiben.

    Manchmal frag ich mich echt...



  • hustbaer schrieb:

    knivil hat ja auch nicht geschrieben dass das mit einem Lambda-Ausdruck allgemein nicht geht.
    Er hat Lambda-Ausdruck mit Capture geschreiben.

    Ah, du meinst weil

    A lambda function that doesn't capture anything can be implicitly converted to a regular function pointer

    Aber das Problem ist doch, dass ich im Deleter die Klassenvariable count benötige. Und die kann ich doch nur an die lambda Funktion übergeben, wenn ich this mittels [this] einfange, um später in der lambda Funktion mittels this->count darauf zuzugreifen.

    Wie kriege ich denn sonst - ohne capture - dieses count in die lambda Funktion?

    Gruß,
    -- Klaus.



  • Wie kriege ich denn sonst - ohne capture - dieses count in die lambda Funktion?

    Nein, die Frage ist, wie du den Typ des Deleters aenderst. Das geht wahrscheinlich nur mit einem std::function-Objekt, da der Typ aus der Lambda-Expression erst im Konstruktor bekannt ist.

    Die andere Frage, welche sich mir aufdrangt, warum benutzt du ueberhaupt einen smart pointer? Der Deleter-Body kann auch vom Destruktor der Klasse uebernommen werden. Move-Konstruktor, Move-Assignment, etc sind trivial. Weiterhin ist die Frage, warum du eine Bibliothek von 1998 benutzt, wenn Zufallszahlen mitlerweile im C++ Standard enthalten sind. Wenn jeder Thread seine eigenen Zufallszahlen bekommen soll, dann erstellt man eben die Objekte zum generieren der Zufallszahlen eben per Thread, oder man erstellt ein Seed-Array zufaellig, oder ...



  • Hallo,

    knivil schrieb:

    Die andere Frage, welche sich mir aufdrangt, warum benutzt du ueberhaupt einen smart pointer? Der Deleter-Body kann auch vom Destruktor der Klasse uebernommen werden.

    Ja, das ist mir bewusst. Ich dachte nur, dass es zum einen guter Programmierstil ist und zum anderen eben eine gute Übung. 😉

    knivil schrieb:

    Weiterhin ist die Frage, warum du eine Bibliothek von 1998 benutzt, wenn Zufallszahlen mitlerweile im C++ Standard enthalten sind. Wenn jeder Thread seine eigenen Zufallszahlen bekommen soll, dann erstellt man eben die Objekte zum generieren der Zufallszahlen eben per Thread, oder man erstellt ein Seed-Array zufaellig, oder ...

    Ja, die Frage ist natürlich berechtig und für mich nicht so einfach zu beantworten. Sagen wir, es ist ein wenig vorgegeben... 🙄

    Anhand deines Vorschlags würde ein Beispiel mittels Open MP so aussehen? Und da gibt es jetzt kein Data Racing, weil jeder Thread seinen eigenen Eintrag des vectors RNGs liest?

    Edit: Vollständiges Minimalbeispiel

    #include <iostream>
    #include <omp.h>
    #include <random>
    #include <vector>
    
    class foo
    {
    	public:
    		foo(unsigned int const seed):_engine( seed ), _rng(0.0,1.0) {}
    		auto operator()() -> double { return _rng( _engine ); }
    
    	private:
    		std::mt19937 _engine;
    		std::uniform_real_distribution<double> _rng;
    };
    
    int main()
    {
    	unsigned int const threads = 4;
    
    	omp_set_num_threads(threads);
    
    	std::vector<foo> RNGs;
    
    	for(unsigned int i = 0; i < threads; ++i)
    		RNGs.push_back( foo( 2 * i ) );
    
    	#pragma omp parallel shared (RNGs)
    	{
    		#pragma omp critical
    		{
    			std::cout << "Thread Nr. " << omp_get_thread_num() << " random number "
    								<< RNGs[ omp_get_thread_num() ]() << std::endl;
    		}
    	}
    
    	return 0;
    }
    

  • Mod

    auto operator()() -> double { return _rng( _engine ); }
    

    Also entweder du lässt das -> double weg, das wäre C++14, oder du schreibst gleich

    double operator()() { return _rng( _engine ); }
    

    .
    Sonst ist das nur überflüssig.



  • Okay,

    und bzgl. meiner Frage zum Data Racing? Gibt es eigentlich eine Möglichkeit auf Data Racing zu prüfen?

    Also nicht einen Code schreiben und rumprobieren, sondern Hilfsmittel verwenden wie gdb zum Debuggen oder valgrind, um Speicherlecks zu finden.

    Noch etwas als Geplapper: Ich frage mich z.B. zur Zeit, ob Data Racing auf eine Funktion stattfinden kann? Ich kann mir vorstellen, dass eine Variable immer nur von einem Thread beschrieben werden kann - aber scheinbar von mehreren gleiczeitig gelesen.

    Aber wie ist das mit einer Funktion? Kann die nur von einem Thread ausgeführt werden? Ein Kumpel meinte einmal salopp, dass so eine Funktion 'einfach auf den Stack gelegt wird', d.h. wenn ein thread eine Funktion benötigt, dann legt er sie auf 'seinen Stack' und verwendet sie einfach?

    Gruß,
    -- Klaus.



  • Funktionen sind ein haufen Bytes die irgendwo im Speicher rumliegen - die werden nicht modifiziert. => Da kann's kein Race geben.
    Auf den Stack gelegt wird eine Funktion auch nicht. Höchstens ihre Parameter, lokalen Variablen, die Rücksprungadresse -- solche Dinge.

    Abgesehen davon kann sich eine Funktion über static Variablen o.ä. natürlich selbst ins Knie schiessen kann. Das Problem dabei sind dann aber wieder die Variablen, nicht die Funktion.

    Was deine erste Frage angeht: es gibt z.B. den ThreadSanitizer ("TSAN"):
    http://code.google.com/p/data-race-test/wiki/ThreadSanitizer
    Und natürlich noch weitere $$$ Tools von Borland, Intel etc.
    Der "korrekte" Weg ist aber zu wissen was man tut, und dann einfach funktionierenden Code zu schreiben.

    EDIT: Und diesen dann ggf. mit solchen Tools nochmal zu überprüfen, das ist natürlich eine sehr gute Sache! Dein Beitrag liest sich allerdings irgendwie nach "ich programmier einfach mal irgendwie, und dann dreh ich das so lange durch mein Debugging-Tools, bis das die Schnauze hält." Und das halte ich für weniger gut 😉



  • hustbaer schrieb:

    EDIT: Und diesen dann ggf. mit solchen Tools nochmal zu überprüfen, das ist natürlich eine sehr gute Sache! Dein Beitrag liest sich allerdings irgendwie nach "ich programmier einfach mal irgendwie, und dann dreh ich das so lange durch mein Debugging-Tools, bis das die Schnauze hält." Und das halte ich für weniger gut 😉

    So extrem ist es sicherlich nicht, aber eben ein Geben und Nehmen. 😉

    Ich muss gestehen, dass mir dies am Programmieren ziemlich gut gefällt, ob etwas funktioniert lässt sich direkt daran erkennen, ob es kompiliert und beim Laufen die erwarteten Ergebnisse produziert.

    Ob es auch wirklich robust ist, zeigt sich dann durch ausprobieren: Mal extreme Werte für Variablen eingeben oder eben z.B. Valgrind drüberlaufen zu lassen, ob der Speicherlecks findet, die nur durch Glück nicht direkt zu Segmentation Faults geführt haben.

    Ansonsten gebe ich dir recht, dass es echt Arbeit ist sich den Aufbau eines Programms zu überlegen: Welche Klasse verwaltet was und wie werden z.B. Informationen von wem zu wem weitergegeben.

    Gruß,
    -- Klaus.



  • Gerade was Data-Races angeht ist es aber relativ einfach korrekten Code zu schreiben.
    Wenn man auf Atomics und andere fancy-schmanzy Sachen verzichtet, und überall brav Mutexen verwendet, dann hat man keine Races.
    Ausser man vergisst irgendwo komplett zu synchronisieren, und diese Fälle werden von TSAN oder ähnlichen Tools noch am Besten gefunden.
    (Wenn man nämlich versucht selbst über Atomics zu synchronisieren, und dabei Mist baut, dann kann es sein dass die Tools die noch vorhandenen Races nicht mehr erkennen können. Oder, je nach Tool, dann haufenweise False Positives ausspucken.)

    Was viel schwieriger ist, ist ein Programm zu schreiben das keine potentiellen Deadlocks enthält.

    Klaus82 schrieb:

    Ich muss gestehen, dass mir dies am Programmieren ziemlich gut gefällt, ob etwas funktioniert lässt sich direkt daran erkennen, ob es kompiliert und beim Laufen die erwarteten Ergebnisse produziert.

    Ich schreib fast nur Code der "unattended" läuft. Serverprogramme, Software für Unterhaltungsspielautomaten - solche Dinge.
    Da müssen nicht nur alle Corner-Cases abgedeckt sein, sondern auch die Fehlerbehandlung passen.
    Und Leaks darf man auch keine haben.
    Das sind Dinge die man mit "schnell mal ausprobieren" nicht verifizieren kann.

    Natürlich wäre hier ganz klar TDD angesagt, das ist ja im Prinzip auch "ausprobieren" -- nur halt viel viel aufwendiger als wenn man einfach nur das fertige Programm testet, ohne zu genau auf Corner-Cases einzugehen.

    Wissen was man tut, und wie man Dinge aufbauen kann so dass die Chance von schweriwegenden Bugs stark reduziert wird macht sich da schon bezahlt.


Anmelden zum Antworten