I am writing a C++ library which will interact on files, memory buffers and remote files accessible with the HTTP protocol.
To handle that, I've decided to create some classes that will use the following interface:
DataStreamInterface.h
class DataStreamInterface {
public:
virtual bool open() = 0;
virtual void close() = 0;
virtual std::streamsize length() const = 0;
virtual std::streamsize tell() const = 0;
virtual std::streamsize seek(std::streamsize position) = 0;
virtual std::streamsize read(char *buffer,
std::streamsize length) = 0;
virtual std::streamsize read(int8_t *buffer) = 0;
virtual std::streamsize read(uint8_t *buffer) = 0;
virtual std::streamsize read(int16_t *buffer) = 0;
virtual std::streamsize read(uint16_t *buffer) = 0;
virtual std::streamsize read(int32_t *buffer) = 0;
virtual std::streamsize read(uint32_t *buffer) = 0;
virtual std::streamsize read(float *buffer) = 0;
virtual std::streamsize read(double *buffer) = 0;
virtual std::streamsize read(std::string *buffer) = 0;
virtual std::streamsize peek(uint8_t *buffer,
std::streamsize length) = 0;
virtual std::streamsize peek(int8_t *buffer) = 0;
virtual std::streamsize peek(uint8_t *buffer) = 0;
virtual std::streamsize peek(int16_t *buffer) = 0;
virtual std::streamsize peek(uint16_t *buffer) = 0;
virtual std::streamsize peek(int32_t *buffer) = 0;
virtual std::streamsize peek(uint32_t *buffer) = 0;
virtual std::streamsize peek(float *buffer) = 0;
virtual std::streamsize peek(double *buffer) = 0;
virtual std::streamsize peek(std::string *buffer) = 0;
virtual std::streamsize write(const char *buffer,
std::streamsize length) = 0;
virtual std::streamsize write(int8_t value) = 0;
virtual std::streamsize write(uint8_t value) = 0;
virtual std::streamsize write(int16_t value) = 0;
virtual std::streamsize write(uint16_t value) = 0;
virtual std::streamsize write(int32_t value) = 0;
virtual std::streamsize write(uint32_t value) = 0;
virtual std::streamsize write(float value) = 0;
virtual std::streamsize write(double value) = 0;
virtual std::streamsize write(const std::string &value) = 0;
virtual ~DataStreamInterface() { }
};
Then I create MemoryDataStream
for reading and writing inside a malloc
'd buffer, FileDataStream
for reading and writing into files and HttpDataStream
for reading remote files.
MemoryDataStream.cc
MemoryDataStream::MemoryDataStream(const DataStreamInit &dsInit) :
_bigEndian(dsInit.bigEndian) {
}
MemoryDataStream::~MemoryDataStream() {
this->_buffer.clear();
}
bool MemoryDataStream::open() {
return true;
}
void MemoryDataStream::close() {
}
std::streamsize MemoryDataStream::length() const {
return this->_buffer.size();
}
std::streamsize MemoryDataStream::seek(std::streamsize position) {
if (position < 0 ||
static_cast<std::streamsize>(this->_cursor) +
position > this->_buffer.size()) {
return -1;
}
this->_cursor = position;
return this->_cursor;
}
std::streamsize MemoryDataStream::tell() const {
return this->_cursor;
}
std::streamsize MemoryDataStream::read(char *buffer,
std::streamsize length) {
std::streamsize result = 0;
for (int i = 0; i < length; i++) {
result += this->_read(buffer++);
}
return result;
}
std::streamsize MemoryDataStream::read(int8_t *buffer) {
return this->_read(buffer);
}
std::streamsize MemoryDataStream::read(uint8_t *buffer) {
return this->_read(buffer);
}
std::streamsize MemoryDataStream::read(int16_t *buffer) {
return this->_read(buffer);
}
std::streamsize MemoryDataStream::read(uint16_t *buffer) {
return this->_read(buffer);
}
std::streamsize MemoryDataStream::read(int32_t *buffer) {
return this->_read(buffer);
}
std::streamsize MemoryDataStream::read(uint32_t *buffer) {
return this->_read(buffer);
}
std::streamsize MemoryDataStream::read(float *buffer) {
return this->_read(buffer);
}
std::streamsize MemoryDataStream::read(double *buffer) {
return this->_read(buffer);
}
std::streamsize MemoryDataStream::read(std::string *buffer) {
std::streamsize i;
std::string result;
i = this->peek(&result);
if (i < 1) {
return i;
}
*buffer = result;
this->_cursor += i;
return i;
}
template <typename T>
std::streamsize MemoryDataStream::_read(T *buffer) {
std::streamsize result = this->_peek(buffer);
if (result > 0) {
this->_cursor += result;
}
return result;
}
std::streamsize MemoryDataStream::peek(uint8_t *buffer,
std::streamsize length) {
std::streamsize result = 0;
for (int i = 0; i < length; i++) {
result += this->_peek(buffer++);
}
return result;
}
std::streamsize MemoryDataStream::peek(int8_t *buffer) {
return this->_peek(buffer);
}
std::streamsize MemoryDataStream::peek(uint8_t *buffer) {
return this->_peek(buffer);
}
std::streamsize MemoryDataStream::peek(int16_t *buffer) {
return this->_peek(buffer);
}
std::streamsize MemoryDataStream::peek(uint16_t *buffer) {
return this->_peek(buffer);
}
std::streamsize MemoryDataStream::peek(int32_t *buffer) {
return this->_peek(buffer);
}
std::streamsize MemoryDataStream::peek(uint32_t *buffer) {
return this->_peek(buffer);
}
std::streamsize MemoryDataStream::peek(float *buffer) {
return this->_peek(buffer);
}
std::streamsize MemoryDataStream::peek(double *buffer) {
return this->_peek(buffer);
}
std::streamsize MemoryDataStream::peek(std::string *value) {
int8_t c;
std::streamsize i;
int size;
std::stringstream strm;
for (i = 0; i < 32768; ++i) {
if (this->_peek(&c) < 1 || c == '\0') {
break;
} else {
this->_cursor += 1;
}
size = i;
strm << c;
}
*value = strm.str();
this->_cursor -= size;
return i;
}
template <typename T>
std::streamsize MemoryDataStream::_peek(T *buffer) {
T value;
T finalValue;
uint8_t *originalData;
uint8_t *finalData;
std::streamsize size = static_cast<std::streamsize>(sizeof(T));
if (static_cast<std::streamsize>(this->_cursor) +
size > this->_buffer.size()) {
return -1;
}
value = *(reinterpret_cast<T*>(&this->_buffer[this->_cursor]));
if (_bigEndian && sizeof(T) > 1) {
originalData = reinterpret_cast<uint8_t*>(&value);
finalData = reinterpret_cast<uint8_t*>(&finalValue);
for (int i = 0; i < sizeof(T); ++i) {
finalData[i] = originalData[(sizeof(T) - i) - 1];
}
value = finalValue;
}
*buffer = value;
return sizeof(T);
}
std::streamsize MemoryDataStream::write(const char *buffer,
std::streamsize length) {
return this->_write(buffer, length);
}
std::streamsize MemoryDataStream::write(int8_t value) {
return this->_write(value);
}
std::streamsize MemoryDataStream::write(uint8_t value) {
return this->_write(value);
}
std::streamsize MemoryDataStream::write(int16_t value) {
return this->_write(value);
}
std::streamsize MemoryDataStream::write(uint16_t value) {
return this->_write(value);
}
std::streamsize MemoryDataStream::write(int32_t value) {
return this->_write(value);
}
std::streamsize MemoryDataStream::write(uint32_t value) {
return this->_write(value);
}
std::streamsize MemoryDataStream::write(float value) {
return this->_write(value);
}
std::streamsize MemoryDataStream::write(double value) {
return this->_write(value);
}
std::streamsize MemoryDataStream::write(const std::string &value) {
return this->_write(value.c_str(), strlen(value.c_str()) + 1);
}
template <typename T>
std::streamsize MemoryDataStream::_write(T buffer,
std::streamsize length) {
size_t pos = static_cast<size_t>(this->_cursor);
size_t size = static_cast<size_t>(length);
if (pos + size > this->_buffer.size()) {
this->_buffer.resize(pos + size);
}
memcpy(&this->_buffer[pos], static_cast<T>(buffer), size);
this->_cursor += size;
return size;
}
template <typename T>
std::streamsize MemoryDataStream::_write(T value) {
T finalValue = value;
uint8_t *originalData = reinterpret_cast<uint8_t*>(&value);
uint8_t *finalData = reinterpret_cast<uint8_t*>(&finalValue);
if (_bigEndian && sizeof(T) > 1) {
for (int i = 0; i < sizeof(T); ++i) {
finalData[i] = originalData[(sizeof(T) - i) - 1];
}
originalData = finalData;
}
return this->_write(originalData, sizeof(T));
}
I would like to ask the following question :
Is it alright to make DataStreamInterface
an abstract class instead of an interface so I can use the templates like this ? Will it affect performance or memory consumption ?
std::streamsize read(T *buffer);
std::streamsize peek(T *buffer);
std::streamsize write(T value);
I've recently realized that any Android and iOS application would freeze when they'll use the library with their own DataStream
implementation.
For instance, if the "C++ side" call the HttpDataStream
class implemented in Java to download a file, it would freeze the whole process and maybe the whole application until the download ends.
Here is an example, calling the remote DataStream class defined in Java:
std::streamsize DataStreamJava::read(double value) {
jmethodID m = jni->GetMethodID(j_dataStream_class_,
"read", "(D)J");
jni->CallLongMethod(j_dataStream_global_, m);
return 0;
}
I've been thinking about creating a class named DataStreamObserver
on the "C++ side", the DataStream
constructor would take an instance of the DataStreamObserver
class then call it every time a read or write operation has finished.
Should I create DataStreamObserver
as an abstract class with templates in order to avoid implementing methods like that?
virtual void onReadSuccess(int8_t value, std::steamsize length) = 0;
virtual void onReadSuccess(uint8_t value, std::steamsize length) = 0;
virtual void onReadSuccess(int16_t value, std::steamsize length) = 0;
virtual void onReadSuccess(uint16_t value, std::steamsize length) = 0;
std::istream
andstd::ostream
. It already handles Memory/Files (and with extensions sockets) \$\endgroup\$