C++ Daten anhand eines char* in unterschiedliche Datein schreiben



  • Hallo zusammen,

    ich bin neu in diesem Forum und recht neu in der Programmierung, also bitte ich um Nachsicht....
    Momentan bin ich dabei ein Programm zu schreiben was Multicastpaket empfängt und die empfangenen Pakete anhand der Absender-IP-Adresse in ein bin file schreibt. (Für jede Ip-Adresse ein eigenes File) Ich bin soweit, dass ich die Daten Empfange und jeweils die Absender-IP-Adresse und die Daten in einem eigenen char* bekomme zusätzlich habe ich noch die länge des Daten Arrays als int.
    Zu meinem Problem, ich weiß nicht so richtiig weiter wie ich dass mit dem schreiben der Daten, bzw mit der filterung nach der IP-Adressen am geschicktesten machen soll.
    Gibt es einen Container der dafür besonders gut geeignet ist oder hat jemand schon mal etwas ähnliches gemacht und kann mit weiter helfen?

    Schon mal vielen Dank für eure Hilfe im vorraus!



  • Da kannst du z.B. eine std::map<Key, T> verwenden.
    Für dich konkret würde sich dann std::map<std::string, std::vector<char>> anbieten, also die IP-Adresse als std::stringfür den Key und ein std::vector<char> für die Daten (die Länge wird dann automatisch vom std::vector mitverwaltet).

    Wenn du jedoch die Daten nach dem Empfang sofort in eine Datei schreiben möchtest, dann kannst du die für die aktuelle IP zugehörige Datei im Append-Modus öffnen und die Daten ohne Datenhaltung sofort rausschreiben.



  • Schon mal vielen Dank für die Antwort, ich habe mich mal etwas über map schlau gemacht und etwas ausprobiert:

    void readdata(char* buffer, int len, const char* sender){
    	map<const char*, string>::iterator it= IPs.find(sender);
    	if (IPs.count(sender)==0) {
    	        string Datei = Dateipfad + "/" + sender +"_"+ Date::get_date() +"_"+ Date::get_time(0)+".txt";
    	        IPs[sender] = Datei;
    	    	Zieldatei.open(Datei,ios::app);
    	    	Zieldatei.write(buffer, len);
    	    	Zieldatei.close();
    	}
    	else if(it != IPs.end()){
    			Zieldatei.open(it->second,ios::app);
    	    	Zieldatei.write(buffer, len);
    	    	Zieldatei.close();
    	}
    	for (auto& x: IPs) {
    	    std::cout << x.first << ": " << x.second << endl;
    	  }
    }
    

    Bin mir nicht sicher ob das so gemeint war....
    Ich möchte die Daten gerne direkt wegschreiben, am liebsten wäre es mir sogar wenn ich die Zieldatei nur einmal pro IP öffnen würde aber bis jetzt ist mir da keine Möglichkeit eingefallen.
    Vllt hat ja jemand Lust mir weiter zu helfen 🙂



  • @Tommy sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Ich möchte die Daten gerne direkt wegschreiben

    Warum tust du es nicht?

    @Tommy sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    die Zieldatei nur einmal pro IP öffnen würde

    Warum schließt du sie dann jedesmal?



  • @Tommy sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    map<const char*,

    ist eine doofe Idee, da hier die Adresse der Schlüssel ist und nicht der Inhalt des Strings. Verwende std::string.



  • @Tommy sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Schon mal vielen Dank für die Antwort, ich habe mich mal etwas über map schlau gemacht und etwas ausprobiert:

    void readdata(char* buffer, int len, const char* sender){
    	map<const char*, string>::iterator it= IPs.find(sender);
    	if (IPs.count(sender)==0) {
                ...
    	}
    	else if(it != IPs.end()){
                ...
    	}
    

    Also erstmal kannst du seit C+11 schreiben auto it = IPs.find(sender); - das ist sehr viel angenehmer und kürzer als die Version mit map<...>::iterator.

    Dann hast du mit dem find im Interator ja schon herausgefunden, ob es das Element gibt - der Check auf .count(sender) == 0 ist also gleichbedeutend mit it == IPs.end(). Du musst also das count nicht zusätzlich aufrufen und und die Bedingung im else kann auch weg. Dann ist auch viel klarar: entweder die Datei ist bereits bekannt oder eben nicht.

    Dann hast du das open...write...close in beiden Zweigen. Könnte auch außerhalb sein. Oder du speicherst gleich eine map von std::string->std::ofstream. Dann musst du die Dateien nicht immer neu öffnen und schließen. (natürlich nur, wenn du nicht beliebig viele IP-Adressen hast - zu viele gleichzeitig offene Dateien sind auch nicht gut).

    Dann hattest du von einem "bin file" geschrieben, tatsächlich sehe ich aber hier .txt-Dateien. Das widerspricht sich irgendwie.



  • Als Zwei-Zeiler geht es auch.

    int main(int argc, char* argv[])
    {
        std::string ip = "127.0.0.1";
        std::string data = "foobar";
        std::string Dateiname = "./" + ip +".txt";
        std::unordered_map<std::string, std::ofstream> Files;
        
        // Sucht nach der IP als Key, falls er den nicht findet, erzeugt er den Eintrag neu. 
        // Liefert aber in jedem Fall in "res.first" den Iterator auf das Element zurueck
        auto res = Files.emplace( std::piecewise_construct,
                                  std::forward_as_tuple( ip ),
                                  std::forward_as_tuple( Dateiname, std::ios::app ) );
        // res.second ist "true" wenn das Element neu erzeugt wurde, false wenn es bereits existierte
        res.first->second.write( data.data(), data.length() );
        return 0;
    }
    
    


  • @It0101 sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Als Zwei-Zeiler geht es auch.

        std::string Dateiname = "./" + ip +".txt";
        std::unordered_map<std::string, std::ofstream> Files;
        
        // Sucht nach der IP als Key, falls er den nicht findet, erzeugt er den Eintrag neu. 
        // Liefert aber in jedem Fall in "res.first" den Iterator auf das Element zurueck
        auto res = Files.emplace( std::piecewise_construct,
                                  std::forward_as_tuple( ip ),
                                  std::forward_as_tuple( Dateiname, std::ios::app ) );
    

    Hm... aus https://en.cppreference.com/w/cpp/container/map/emplace:

    The element may be constructed even if there already is an element with the key in the container, in which case the newly constructed element will be destroyed immediately.

    Klingt für mich nicht so, als wollte ich das mit ofstream benutzen. Oder verstehe ich das falsch?



  • Das wäre ja verrückt 😃 ( Edit: und es ist leider wahr 😆 )

    Also https://www.cplusplus.com/reference/map/map/emplace/

    steht es so:
    The insertion only takes place if no other element in the container has a key equivalent to the one being emplaced (keys in a map container are unique).

    Und das kann ich auch aus meiner Erfahrung bestätigen, da ich mit unorderedmaps fast nur so arbeite. Das wäre mir schon aufgefallen, wenn die Objekte jedes mal neu konstruiert würden. Das beste an dieser Art von emplace ist, dass man das auch mit Objekten machen kann, die weder kopierbar noch bewegbar sind, was bei mir relativ oft vorkommt.

    Dein Link sagt:
    *Inserts a new element into the container constructed in-place with the given args if there is no element with the key in the container.

    Careful use of emplace allows the new element to be constructed while avoiding unnecessary copy or move operations. The constructor of the new element (i.e. std::pair<const Key, T>) is called with exactly the same arguments as supplied to emplace, forwarded via std::forward<Args>(args).... The element may be constructed even if there already is an element with the key in the container, in which case the newly constructed element will be destroyed immediately.*

    Edit: der Link ist korrekt. Es wird temporär ein "unnützes" Objekt erzeugt. Das hatte ich ehrlich gesagt so nicht auf dem Schirm. In dem Fall ist die Lösung natürlich für ein Stream-Objekt nicht unbedingt geeignet.
    Mir ist allerdings nicht klar, warum sich emplace so verhält, denn diese Variante mit "piecewise" und "forward" funktioniert auch mit Klassen, die man weder bewegen noch kopieren kann.



  • Ist vielleicht implementation defined. "May be" heißt ja nicht "wird garantiert immer".



  • @It0101 sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Mir ist allerdings nicht klar, warum sich emplace so verhält, denn diese Variante mit "piecewise" und "forward" funktioniert auch mit Klassen, die man weder bewegen noch kopieren kann.

    Das hat ja mit move oder copy erstmal nichts zu tun. Eine Map enthält std::pairs. Um zu checken, ob ein Element bereits vorhanden ist, brauche ich aber den Key aus dem Pair. Der Key im pair muss also auf auf jeden Fall konstruiert werden. - wie soll denn die Map sonst herausfinden, ob ein Element schon vorhanden ist? Und dann wird wohl eben gleich das ganze pair erzeugt - und eben wieder verworfen, wenn bereits in der Map.

    (die gesamte obere Hälfte deines Beitrags hat mit diesem Phänomen irgendwie nichts zu tun)

    Ich glaube, das hat eben wieder die pairs als Ursache. Dass eine map auf pairs basiert, ist wohl der grundlegende Fehler.



  • 
    class A
    {
    public:
        A( const std::string &foobar_ ) { std::cout << "ctr: " << foobar_<< "\n"; }
        A( const A & ) = delete;
        A( A && ) = delete;
        A &operator=( const A & ) = delete;
        A &operator=( A && ) = delete;
    };
    
    
    int main(int argc, char* argv[])
    {
        std::unordered_map<std::string, A> Files;
        std::string Key = "x";
        Files.try_emplace( /*std::piecewise_construct, */Key, "test" );
        Files.try_emplace( /*std::piecewise_construct, */Key, "test3" );
        return 0;
    }
    

    Habe gerade nochmal recherchiert:
    Wenn man "try_emplace" verwendet, wird nur einmal der Ctr aufgerufen.



  • @wob sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Das hat ja mit move oder copy erstmal nichts zu tun. Eine Map enthält std::pairs. Um zu checken, ob ein Element bereits vorhanden ist, brauche ich aber den Key aus dem Pair. Der Key im pair muss also auf auf jeden Fall konstruiert werden. - wie soll denn die Map sonst herausfinden, ob ein Element schon vorhanden ist? Und dann wird wohl eben gleich das ganze pair erzeugt - und eben wieder verworfen, wenn bereits in der Map.

    Naja, aber wozu wird denn der Value_Type instanziiert? Es wird eine Instanz von "A" erzeugt, aber dann weder kopiert noch bewegt ( geht ja auch gar nicht ). Dass der Key_type erzeugt wird, ist ja kein Problem und auch nachvollziehbar. Aber wenn es den Key schon gibt, sehe ich keinen Grund warum der Value_type anlegt werden muss.

    Hier ist ja auch die Rede von "Inplace"Konstruktion, das klang in meinem Kopf immer nach "wird direkt im Map-Speicher erzeugt, wenn notwendig.



  • @It0101 sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Naja, aber wozu wird denn der Value_Type instanziiert? ... Aber wenn es den Key schon gibt, sehe ich keinen Grund warum der Value_type anlegt werden muss.

    Dann erzeuge doch mal ein pair<string, ofstream>, ohne dass du den ofstream auch erzeugst.



  • @wob sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    @It0101 sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Naja, aber wozu wird denn der Value_Type instanziiert? ... Aber wenn es den Key schon gibt, sehe ich keinen Grund warum der Value_type anlegt werden muss.

    Dann erzeuge doch mal ein pair<string, ofstream>, ohne dass du den ofstream auch erzeugst.

    Warum muss denn die Map das Pair überhaupt erzeugen, wenn der Key schon existiert? "Inplace" ist halt für mich was anderes als "ich mach erstmal das pair, egal ob ichs brauch oder nicht"



  • @It0101 sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    @wob sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    @It0101 sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Naja, aber wozu wird denn der Value_Type instanziiert? ... Aber wenn es den Key schon gibt, sehe ich keinen Grund warum der Value_type anlegt werden muss.

    Dann erzeuge doch mal ein pair<string, ofstream>, ohne dass du den ofstream auch erzeugst.

    Warum muss denn die Map das Pair überhaupt erzeugen, wenn der Key schon existiert? "Inplace" ist halt für mich was anderes als "ich mach erstmal das pair, egal ob ichs brauch oder nicht"

    Wie willst du sonst den Check machen?

    Die Alternative wäre, dass du den Key 2x erzeugst. Einmal für dich zum Vergleichen und wenn nicht gefunden, nochmal für den Wert im Pair.



  • @wob sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Die Alternative wäre, dass du den Key 2x erzeugst. Einmal für dich zum Vergleichen und wenn nicht gefunden, nochmal für den Wert im Pair.

    Klingt für mich billiger als das ganze Pair zu erzeugen. Ein Key ist ja im allgemeinen "leichtgewichtiger" als der value_type.



  • @It0101 sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    Klingt für mich billiger als das ganze Pair zu erzeugen.

    1. kommt darauf an. Vielleicht ist dein Key-Objekt auch groß und teurer zu erstellen als der Value.
    2. Gerade weil du ja auch move verwenden willst: kannst du den Key überhaupt 2x aus den Daten erstellen? Ggf. willst du ja einen Constructor verwenden, der von den übergebenen Daten movet.

    Ist also nicht so einfach.

    Was ich mir noch gedacht hatte: ein pair<keytype, char[sizeof valuetype]> zu erzeugen und dann den value per reinterpret_cast & placement-new darin erzeugen, sofern benötigt. Gibt aber bestimmt irgendwelche Gründe, die dagegen sprechen, wahrscheinlich UB oder so 🙂



  • Je länger man drüber nachdenkt, desto komplexer wird das Thema. Vermutlich erzeugt man wirklich das Pair und arbeitet dann damit und erzeugt es dann im Nachgang inplace nochmal und kann dadurch auch NonCopyAble und co. sauber verarzten. Aber interessant, dass zumindest die MinGW-implementation von try_emplace die Nummer dann sauber hinbekommt.

    https://stackoverflow.com/questions/46046828/is-there-any-reason-to-use-stdmapemplace-instead-of-try-emplace-in-c1z

    Interessant. Also try_emplace bedingt einen kopierbaren Key. Das ist scheinbar der einzige Haken. Emplace kann auch mit NonCopyAble-Keys sauber arbeiten, und hat dafür als Nachteil diesen zusätzlichen Konstruktoraufruf.

    Das heißt für die meisten Anwendungsfälle ist try_emplace the way to got, wie mir scheint.



  • @It0101 sagte in C++ Daten anhand eines char* in unterschiedliche Datein schreiben:

    und erzeugt es dann im Nachgang inplace nochmal

    Hä? Warum würdest du es nochmal erzeugen wollen?


Log in to reply