What I'd like to do is read data from a stream (like ifstream::read), but reserve N = 4 bytes from being returned. The last 4 bytes of the stream are not part of the data itself, but are metadata.
In the general case, the stream is not an ifstream so I won't be able to seek, putback, or determine the size of the stream ahead of time. Here I'm just using it as an example.
My approach is to keep a reserve buffer of 4 bytes to ensure I never accidentally return the last four bytes of the stream. However, the logic turned out to be pretty hard to manage... I'm hoping there's a cleaner way to accomplish what I want.
I'd like to ask for a code review in terms readability (is there a better approach?) and/or performance (is there anything I'm doing that is suboptimal?)
#include <iostream>
#include <array>
#include <vector>
#include <fstream>
#include <cstring>
#define RESERVE_SIZE 4
static std::array<char, RESERVE_SIZE> reserve = {0,0,0,0}; // buffer for holding bytes at end of stream
static bool first_read = true; // true if first read and reserve buffer is empty
// helper function to print output
void data_printer(std::string msg, char * bd, size_t bytes_read) {
std::cout << msg << bytes_read << ": ";
for(size_t i=0; i < bytes_read; i++) std::cout << (int)bd[i] << " ";
std::cout << std::endl;
}
// helper function to read and return # of bytes read
size_t read_count(std::ifstream & myFile, char * dst, size_t length) {
myFile.read(dst, length);
return myFile.gcount();
}
// dst -- output buffer, has at least "length" bytes
// exact -- if true, expect "length" bytes read from myFile and copied to dst; throw error if less than that
// return value is the number of bytes read from my File and copied to dst
size_t read_reserve(std::ifstream & myFile, char * dst, size_t length, bool exact=false) {
if(first_read) {
myFile.read(reserve.data(), RESERVE_SIZE);
first_read = false;
}
if(exact) {
if(length >= RESERVE_SIZE) {
std::memcpy(dst, reserve.data(), RESERVE_SIZE);
myFile.read(dst + RESERVE_SIZE, length - RESERVE_SIZE);
myFile.read(reserve.data(), RESERVE_SIZE);
if(myFile.gcount() != RESERVE_SIZE) throw std::runtime_error("not enough data in file :(");
return length;
} else { // RESERVE_SIZE > length
std::memcpy(dst, reserve.data(), length);
// since some of the reserve buffer was consumed, shift the unconsumed bytes to beginning of array
// then read from file to fill up reserve buffer
std::memmove(reserve.data(), reserve.data() + length, RESERVE_SIZE - length);
myFile.read(reserve.data() + RESERVE_SIZE - length, length);
if(myFile.gcount() != length) throw std::runtime_error("not enough data in file :(");
return length;
}
} else { // !exact -- we can't assume that "length" bytes are left in myFile; there could even be zero bytes left
if(length >= RESERVE_SIZE) {
// use "dst" as a temporary buffer, since it's already allocated
// it is not a good idea to allocate a temp buffer of size length, as length can be large
std::memcpy(dst, reserve.data(), RESERVE_SIZE);
size_t n_read = read_count(myFile, dst + RESERVE_SIZE, length - RESERVE_SIZE);
size_t n_bufferable = n_read + RESERVE_SIZE;
if(n_bufferable < length) {
std::memcpy(reserve.data(), dst + n_bufferable - RESERVE_SIZE, RESERVE_SIZE);
return n_bufferable - RESERVE_SIZE;
} else {
std::array<char, RESERVE_SIZE> temp_buffer = {0,0,0,0};
size_t temp_size = read_count(myFile, temp_buffer.data(), RESERVE_SIZE);
std::memcpy(reserve.data(), dst + n_bufferable - (RESERVE_SIZE - temp_size), RESERVE_SIZE - temp_size);
std::memcpy(reserve.data() + RESERVE_SIZE - temp_size, temp_buffer.data(), temp_size);
return n_bufferable - (RESERVE_SIZE - temp_size);
}
} else { // length < RESERVE_SIZE
std::vector<char> temp_buffer(length, '\0');
size_t return_value = read_count(myFile, temp_buffer.data(), length);
// n_bufferable is at most RESERVE_SIZE*2 - 1 = 7
std::memcpy(dst, reserve.data(), return_value);
std::memmove(reserve.data(), reserve.data() + return_value, RESERVE_SIZE - return_value);
std::memcpy(reserve.data() + (RESERVE_SIZE - return_value), temp_buffer.data(), return_value);
return return_value;
}
}
}
// example function usage
int main() {
std::ofstream outfile("/tmp/temp.bin", std::ios::out | std::ios::binary);
std::array<char, 30> mydata = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29};
outfile.write(mydata.data(), 30);
outfile.close();
std::cout << "test1" << std::endl;
std::ifstream infile("/tmp/temp.bin", std::ios::in | std::ios::binary);
std::vector<char> buffer(100, '\0');
size_t bytes_read;
char * bd = buffer.data();
bytes_read = read_reserve(infile, bd, 3, true); data_printer("bytes read: ", bd, bytes_read); data_printer("reserve: ", reserve.data(), RESERVE_SIZE);
bytes_read = read_reserve(infile, bd, 3, false); data_printer("bytes read: ", bd, bytes_read); data_printer("reserve: ", reserve.data(), RESERVE_SIZE);
bytes_read = read_reserve(infile, bd, 5, true); data_printer("bytes read: ", bd, bytes_read); data_printer("reserve: ", reserve.data(), RESERVE_SIZE);
bytes_read = read_reserve(infile, bd, 5, false); data_printer("bytes read: ", bd, bytes_read); data_printer("reserve: ", reserve.data(), RESERVE_SIZE);
bytes_read = read_reserve(infile, bd, 100, false); data_printer("bytes read: ", bd, bytes_read); data_printer("reserve: ", reserve.data(), RESERVE_SIZE);
infile.close();
std::cout << "test2" << std::endl;
first_read = true;
std::ifstream infile2("/tmp/temp.bin", std::ios::in | std::ios::binary);
bytes_read = read_reserve(infile2, bd, 28, false); data_printer("bytes read: ", bd, bytes_read); data_printer("reserve: ", reserve.data(), RESERVE_SIZE);
bytes_read = read_reserve(infile2, bd, 3, false); data_printer("bytes read: ", bd, bytes_read); data_printer("reserve: ", reserve.data(), RESERVE_SIZE);
infile2.close();
}
// g++ -std=c++11 -O3 read_reserve.cpp -o test