• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

C++ Dateien kopieren - Meinungen zum Code

Kenobi van Gin

Brillenschlange

Registriert
14 Juli 2013
Beiträge
3.620
Ort
.\
Hallo zusammen.

Ich melde mich mal wieder mit einer Frage zu C++ :) Nachdem in meinem Referenzbuch gerade das Thema lesen und schreiben in binären Dateien behandelt wurde, wollte ich gern eine eigene Konsolenanwendung im Stil von z.B. xcopy schreiben. Der Hintergrund ist, dass ich nach der Erstellung meines Backups (mit Personal Backup 5) als externes Programm immer eine Batch aufrufe, die via xcopy zwei TrueCrypt-Container kopiert. Da das unter bestimmten Umständen nicht reibungslos funktioniert (und ich ja auch noch etwas lernen will), habe ich mir nun also einen eigenen Code zum Kopieren von Dateien geschrieben.

[src=cpp]void copyFile(string strSourcePath, string strDestinationPath)
{

ifstream readData;
ofstream writeData;

readData.open(strSourcePath, ios_base::binary | ios_base::in);
writeData.open(strDestinationPath, ios_base::binary | ios_base::out);

unsigned long long lngSourceFileSize = getFileSize(strSourcePath);
unsigned long long lngBytesRead = 0;
int intCurrPercentage = 0;
int intLastPercentage = 0;

cout << " Copying file:" << endl << " 0 % done";
char Buffer;
while (readData.read(&Buffer, sizeof(Buffer)))
{
writeData.write(&Buffer, sizeof(Buffer));
lngBytesRead += sizeof(Buffer);
intCurrPercentage = round(100.0 / lngSourceFileSize * lngBytesRead);
if (intCurrPercentage > intLastPercentage)
{
cout << '\r';
cout << " " << intCurrPercentage << " % done";
intLastPercentage = intCurrPercentage;
}

/*
if (intCurrPercentage > intLastPercentage + 1)
{
cout << '\r';
cout << intCurrPercentage << " % |";
for (int i = 0; i <= intCurrPercentage; i++)
{
if (!(i % 2))
cout << '=';
}
cout << string(100 - intCurrPercentage, ' ') << "|";
intLastPercentage = intCurrPercentage;
}
*/
}
cout << endl << " Done!" << endl;
readData.close();
writeData.close();

}[/src]

Der auskommentierte Teil sollte eine einfach Progressbar anzeigen. Das hat auch im Prinzip funktioniert, war aber noch verbuggt und hat außerdem gefühlt einiges an Performance gefressen. Darum habe ich es vorerst durch eine einfach %-Anzeige ersetzt.

Erstmal meine eigenen Gedanken zum Code:
1.) Ist diese Art, Dateien zu kopieren, prinzipiell eine einigermaßen effiziente? Oder gibt es da Möglichkeiten, die wesentlich performanter oder weniger fehleranfällig sind?
2.) Der verwendete Lesepuffer ist hier, wie man sieht, nur 1 char (= 1 Byte?) groß. Ursprünglich wollte ich einen wesentlich größeren Puffer. Leider stellt sich da natürlich das Problem, dass bei Dateien, deren Größe kein Vielfaches der Puffergröße ist, jeweils die letzten Bytes nicht mehr kopiert werden. Nun könnte ich natürlich eine zusätzliche if-Abfrage einbauen, die dann für die letzten Bytes den Puffer wieder auf 1 setzt. Würdet ihr das so machen, oder gibt es da einen eleganteren Weg?
3.) Besonders bei großen Dateien fällt auf, dass meine Methode nicht die performanteste ist. Würde hier ein größerer Buffer (z.B. 128 oder sogar 256 Bytes) Abhilfe schaffen?
4.) Im Augenblick überprüft der Code noch nicht auf Fehler beim Öffnen der Ein- und Ausgabedatei. Das ist mir bewusst. Füge ich später noch hinzu ;)

Ich bin gespannt auf eure Einschätzungen und vermute fast, dass ich es danach dann doch wieder komplett umschmeißen muss :D Aber ich bin ja hier, um zu lernen :)
 

Asseon

Draic Kin

Registriert
14 Juli 2013
Beiträge
10.353
Ort
Arcadia
Wenn du einen c++17 fähigen compiler hast schau dir mal die Filesystem library an.

Ansonsten ist ein 1 byte buffer eher … unzureichend, vor allem unnötig:

cppreference about the read function schrieb:
<snip>
Characters are extracted and stored until any of the following conditions occurs:
  • count characters were extracted and stored
  • end of file condition occurs on the input sequence (in which case, setstate(failbit|eofbit) is called). The number of successfully extracted characters can be queried using gcount().
<snip>
Du kannst also einfach mit folgendem code prüfen wie viel gelesen wurde und exakt so viel schreiben, sobald du weniger als sizeof(buf) gelesen hast brichst du dann ab.
[src=cpp]
ifstream readData;
ofstream writeData;

char buf[1024];
std::streamsize bytes_read = sizeof(buf);

readData.read(&buf, sizeof(buf));
// std::streamsize ist der return wert von gcount, das ist einfach ein integer typ der garantiert groß genug ist.
std::streamsize bytes_read = readData.gcount();
//do fancy shit

[/src]
Den sonderfall abfangen.


Ansonten gibt es noch diese viel zu einfach Variante:
[src=cpp]writeData << readData.rdbuf();[/src]
Wobei das natürlich keinen status printed.

Außerdem möchte ich erwähnen, das es immer weider sonderfälle gibt die recht kompliziert werden können, sachen wie alternate data streams, Berechtigungen, datei Attribute und vermutlich noch ein par sachen die mir nicht einfallen.
Die sind mit libs wie der eingangs erwähnten filesystem lib typischerweise deutlich einfacher zu Behandeln.
 

Kenobi van Gin

Brillenschlange

Registriert
14 Juli 2013
Beiträge
3.620
Ort
.\
  • Thread Starter Thread Starter
  • #3
Wenn du einen c++17 fähigen compiler hast schau dir mal die Filesystem library an.
Da bin ich mir nicht so ganz sicher :D Ich nutze Code::Blocks mit Mingw unter Windows. Es gibt zwar in der IDE jeweils einen Haken für C++11, C++14 und C++17, aber ich war mir stellenweise schon unsicher, ob das wirklich schon unterstützt wird. Hatte allerdings bei meiner eigenen Recherche auch schon was von der besagten lib gelesen, dann werde ich mir das wohl nochmal reintun.

Ansonsten ist ein 1 byte buffer eher … unzureichend, vor allem unnötig:
Jo, klar. Und eben vermutlich ja auch mies für die Performance.

Du kannst also einfach mit folgendem code prüfen wie viel gelesen wurde und exakt so viel schreiben, sobald du weniger als sizeof(buf) gelesen hast brichst du dann ab.
Danke! Das war letztlich genau das, was ich gesucht habe :T

Ansonten gibt es noch diese viel zu einfach Variante:
[src=cpp]writeData << readData.rdbuf();[/src]
Dazu müsste ich also nur die beiden Files öffnen und dann deine Zeile einbauen? Den Buffer setzt er dann selbst auf die passende Größe? Da gibts aber dann keine Möglichkeit, eine Fortschrittsanzeige einzubauen, wenn ich dich richtig verstanden hab?

Außerdem möchte ich erwähnen, das es immer weider sonderfälle gibt die recht kompliziert werden können, sachen wie alternate data streams, Berechtigungen, datei Attribute und vermutlich noch ein par sachen die mir nicht einfallen.
Die sind mit libs wie der eingangs erwähnten filesystem lib typischerweise deutlich einfacher zu Behandeln.
Jo, klar. Berechtigungen sind noch so eine Sache. Im Zweifelsfall müsste ich dann eben, falls nötig, das Programm manuell als Admin ausführen. Ich nutze das ja sowieso nur selber.
Was mir allerdings aufgefallen ist: Offenbar kann das Programm (zumindest, wenn ich es aus der IDE heraus starte) keine Dateien öffnen, deren Pfade "besondere Zeichen" (Umlaute, Vokale mit Akzenten, ...) enthalten :unknown: Muss aber nochmal testen, ob das vielleicht einfach an der Locale liegt, die die IDE vorgibt und aus der Eingabeaufforderung heraus funktioniert.

Danke jedenfalls schonmal für deine Hinweise!

[EDIT:]

So, mal fix alles eingebaut. Deine Tipps waren Gold wert :)

Mein Code sieht jetzt erstmal so aus:

[src=cpp]void copyFile(string strSourcePath, string strDestinationPath)
{

unsigned long long lngSourceFileSize = getFileSize(strSourcePath);
unsigned long long lngBytesRead = 0;
int intCurrPercentage = 0;
int intLastPercentage = 0;
int intRefreshRate = 1;

if (lngSourceFileSize < 10485760)
{
intRefreshRate = 20;
}
else if (lngSourceFileSize < 1048576000)
{
intRefreshRate = 10;
}
else if (lngSourceFileSize < 10485760000)
{
intRefreshRate = 1;
}
else
{
intRefreshRate = 0;
}

ifstream readData;
ofstream writeData;

readData.open(strSourcePath, ios_base::binary | ios_base::in);
writeData.open(strDestinationPath, ios_base::binary | ios_base::out);

cout << " Copying file:" << endl << " 0 % done";
char Buffer[1024];
streamsize bytes_read;

while (true)
{
if (readData.eof())
break;

readData.read(&Buffer[0], sizeof(Buffer));
bytes_read = readData.gcount();
writeData.write(&Buffer[0], bytes_read);

lngBytesRead += bytes_read;
intCurrPercentage = round(100.0 / lngSourceFileSize * lngBytesRead);

if (intCurrPercentage >= intLastPercentage + intRefreshRate)
{
cout << '\r';
cout << " " << intCurrPercentage << " % done";
intLastPercentage = intCurrPercentage;
}

}
cout << endl << " Done!" << endl;
readData.close();
writeData.close();

}[/src]

Die RefreshRate habe ich eingebaut, damit bei kleineren Dateien keine Performance durch zu häufige Aktualisierung der Forschrittsanzeige flöten geht. Funktioniert auch ganz gut, soweit ich das beurteilen kann.
Mit dem größeren Buffer jetzt geht es auch wesentlich schneller. Hatte ich mir ja schon gedacht :D Die eof()-Abfrage habe ich an den Anfang der Schleife gepackt, falls eine sehr kleine Datei schon nach einem Schleifendurchgang zuende ist. Ansonsten würde ja wieder irgendwelcher random RAM-Inhalt in die Datei geschrieben.

Prima! Dann fehlt jetzt eigentlich nur noch die Fehlerbehandlung! Danke :)
 
Zuletzt bearbeitet:

FirleFranz

Neu angemeldet

Registriert
17 Okt. 2018
Beiträge
1
Hallo Kenobi van Gin,

ein paar kleine Anmerkungen zu deinen Code hätte ich auch:

1)
System Calls (insbesondere File IO) können Exceptions werfen, d.h. es ist ratsam um dein
[src=cpp]
readData.open(strSourcePath, ios_base::binary | ios_base::in);
writeData.open(strDestinationPath, ios_base::binary | ios_base::out);
[/src]
Exception Handling zu packen. Etwa so:
[src=cpp]
try { /* open ...*/ }
catch(...) { /* fängt alle ausnahmen */ }
[/src]

2)
Dein Code sieht leider noch sehr nach C und nicht nach (neuerem) C++ aus.
Neben dem vorgeschlagenen Iostream-Operatoren könntest du noch „unsigned long long“ zu „uint64_t“ umwandeln.

3)
Falls du weiterhin den Old School C Weg mit dem Buffer gehen willst könntest du die Performance Probleme auch damit lösen, dass du nicht bei jeder Iteration ein read/write machst sondern für „MAX_BUFF_SIZE“ guckst, ob genug Bytes in der Quelldatei vorliegen und wenn ja, kopierst du dann eben MAX_BUFF_SIZE Bytes in einem read/write (beim letzten Durchlauf eben weniger).
 
Zuletzt bearbeitet:

Kenobi van Gin

Brillenschlange

Registriert
14 Juli 2013
Beiträge
3.620
Ort
.\
  • Thread Starter Thread Starter
  • #5
@FirleFranz: Danke für die Tipps :)
Try/catch "kann" ich bei C++ noch nicht. Aber vielleicht wäre es trotzdem gut, mich damit mal zu beschäftigen. (Bei VB hatte ich das seinerzeit schon benutzt.)

Dass es für die Datentypen inzwischen neue Bezeichnungen gibt, war mir nicht bewusst. Wie gesagt, ich lerne aus einem Buch (nicht mehr ganz aktuell, das ist zum C++11-Standard erschienen), aber sooo alt ja auch noch nicht :D Das behalte ich mal im Hinterkopf.

Das mit dem größeren Buffer mache ich ja jetzt auch schon. Würdest du einen noch größeren wählen? Soweit ich weiß wird doch aber bei C++ sowieso nicht bei "write()" sofort in die Datei geschrieben, sondern wiederum gepuffert, bis der Puffer voll ist oder die Datei geschlossen wird, oder? Hatte ich jedenfalls so verstanden.
 

GoPro

visual basic

Registriert
25 Juni 2016
Beiträge
108
Try/Catch sollte auch nur als Sicherheitsnetz verwendet werden, nicht als Ersatz für mögliche Validierung im Vorfeld, à la ON ERROR GO NEXT. ;)
 

Kenobi van Gin

Brillenschlange

Registriert
14 Juli 2013
Beiträge
3.620
Ort
.\
  • Thread Starter Thread Starter
  • #7
@Asseon: Grad mal son bisschen durch die Referenz zur Filesystem lib gescrollt. Das liest sich alles sehr interessant! Danke für den Tipp. Da werde ich mich in den nächsten Tagen mal mehr mit beschäftigen :)
...und hoffen, dass meine IDE bzw. der Compiler das unterstützt :D
 

Asseon

Draic Kin

Registriert
14 Juli 2013
Beiträge
10.353
Ort
Arcadia
Dass es für die Datentypen inzwischen neue Bezeichnungen gibt, war mir nicht bewusst.
Die gibt es auch nicht.
Der unterschied ist, dass uint64_t eine klar definierte Länge hat "unsigned long long" dagegen nicht, letztere muss "nur" mindestens 64 bit groß sein.
In vielen fällen brauch man die Garantie exakter größe aber gar nicht, daher ist es vollkommen in Ordnung auch die typen, short, int, long int und long long int weiterhin zu verwenden.
 

Kenobi van Gin

Brillenschlange

Registriert
14 Juli 2013
Beiträge
3.620
Ort
.\
  • Thread Starter Thread Starter
  • #9
Wollte mich nun die Tage mal näher mit der Filesystem-Library beschäftigen, aber mein Compiler schmeißt immer einen "directory not found"-Fehler. Sieht ja so aus, als würde der das noch nicht unterstützen?! Dabei ist das doch schon von 2017 :unknown:
 

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Du brauchst eine Standard-Library, die std::filesystem unterstützt.

MinGW ist ja ein Windows-Port der GNU-Toolchain, also GCC (Compiler) und libstdc++ (Standardlib). std::filesystem ist für libstdc++ natürlich erstmal für die unix-basierten Plattformen implementiert worden. MinGW hatte trotz aktueller GCC-Version noch keine Unterstützung. Inzwischen scheint sich das geändert zu haben.

Ohne die exakte Fehlermeldung lässt sichs schwer 100% beurteilen, sieht mir aber so aus, als wäre dein MinGW zu alt. Updates gibts beim Sourceforge-Projekt von MinGW-w64. I.d.R. willst du 64bit, POSIX-Threads und SEH-Exceptions. Das heißt im Moment dieser Download (MinGW-w64 GCC 8.1.0).

Alternativ: boost::filesystem ist sehr ähnlich und brauch iirc nicht mal C++11.
 

Kenobi van Gin

Brillenschlange

Registriert
14 Juli 2013
Beiträge
3.620
Ort
.\
  • Thread Starter Thread Starter
  • #11
Ja, hatte mich auch schon gewundert, dasss es für die gesamte IDE so selten Updates gibt. Aber wenn ich den Compiler manuell updaten muss, dann erklärt es das natürlich. Danke schonmal für den Hinweis!
 
Oben