2
\$\begingroup\$

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;
\$\endgroup\$
  • \$\begingroup\$ You realize that C++ already has a stream interface std::istream and std::ostream. It already handles Memory/Files (and with extensions sockets) \$\endgroup\$ – Martin York Jul 31 '15 at 17:18
  • \$\begingroup\$ You're right, but I need to read binary data with multiple types of variable such as byte, char, short, int... And I would like to have a kind of class I can use with mobile apps made in Java and Objective-C since I need to do the bindings in those languages. \$\endgroup\$ – Axel Isouard Jul 31 '15 at 17:21
  • \$\begingroup\$ Loki Astari, I've edited the question with a call to the Java class from the "C++ side". \$\endgroup\$ – Axel Isouard Jul 31 '15 at 17:23
  • 1
    \$\begingroup\$ Also note. My implementation of ThorsStream also provides a class that does the download in the background. Your code can read the stream like normal in the main thread but will only block if it tries to read more than has currently been downloaded. \$\endgroup\$ – Martin York Jul 31 '15 at 17:25
  • \$\begingroup\$ Have to go to work now. But I'll write a review this afternoon. \$\endgroup\$ – Martin York Jul 31 '15 at 17:26
3
\$\begingroup\$

Interface

Seems like none of these methods should actually be virtual.

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() { }
};

In your implementation you add _write(), _read() and _peek() that does the actual work. Seems like these are really your virtual functions the others should just be implemented in the base class DataStreamInterface to use these virtual functions.

I think I would implement like this:

class DataStreamInterface {
 public:
    // Virtual Interface:
    virtual ~DataStreamInterface() { }

    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;
 private:
    // All the interesting stuff for each class is encapsulated in
    // these threee virtual methods. All the other read/peek/write
    // methods should delagate their work to these and not need to 
    // be re-implemented in each class.
    virtual std::streamsize vread(char *buffer, std::size_t size) = 0;
    virtual std::streamsize vwrite(char *buffer, std::size_t size) = 0;
    virtual std::streamsize vpeek(char *buffer, std::size_t size) = 0;

    template<typename T>
    std::streamsize tread(T *buffer, std::size_t size = sizeof(T))
    {
        return vread(reinterpret_cast<char*>(buffer), size);
    }
    template<typename T>
    std::streamsize twrite(T *buffer, std::size_t size = sizeof(T))
    {
        return vwrite(reinterpret_cast<char*>(buffer), size);
    }
    template<typename T>
    std::streamsize tpeek(T *buffer, std::size_t size = sizeof(T))
    {
        return vpeak(reinterpret_cast<char*>(buffer), size);
    }

    // standard interface.
    // I did these in a hurry there will be mistakes.
  public: 
    std::streamsize read(char *buffer, std::streamsize length){return tread(buffer, length);}
    std::streamsize read(int8_t *buffer)   {return tread(buffer);}
    std::streamsize read(uint8_t *buffer)  {return tread(buffer);}
    std::streamsize read(int16_t *buffer)  {return tread(buffer);}
    std::streamsize read(uint16_t *buffer) {return tread(buffer);}
    std::streamsize read(int32_t *buffer)  {return tread(buffer);}
    std::streamsize read(uint32_t *buffer) {return tread(buffer);}
    std::streamsize read(float *buffer)    {return tread(buffer);}
    std::streamsize read(double *buffer)   {return tread(buffer);}
    std::streamsize read(std::string *buffer) {return tread(buffer);}
    std::streamsize peek(uint8_t *buffer, std::streamsize length) {return tpeek(buffer, length);}
    std::streamsize peek(int8_t *buffer)   {return tpeek(buffer);}
    std::streamsize peek(uint8_t *buffer)  {return tpeek(buffer);}
    std::streamsize peek(int16_t *buffer)  {return tpeek(buffer);}
    std::streamsize peek(uint16_t *buffer) {return tpeek(buffer);}
    std::streamsize peek(int32_t *buffer)  {return tpeek(buffer);}
    std::streamsize peek(uint32_t *buffer) {return tpeek(buffer);}
    std::streamsize peek(float *buffer)    {return tpeek(buffer);}
    std::streamsize peek(double *buffer)   {return tpeek(buffer);}
    std::streamsize peek(std::string *buffer) {return tpeek(buffer.c_str(), buffer->length());}
    std::streamsize write(const char *buffer,std::streamsize length) {return tpeek(buffer, length);}
    std::streamsize write(int8_t value)    {return twrite(buffer);}
    std::streamsize write(uint8_t value)   {return twrite(buffer);}
    std::streamsize write(int16_t value)   {return twrite(buffer);}
    std::streamsize write(uint16_t value)  {return twrite(buffer);}
    std::streamsize write(int32_t value)   {return twrite(buffer);}
    std::streamsize write(uint32_t value)  {return twrite(buffer);}
    std::streamsize write(float value)     {return twrite(buffer);}
    std::streamsize write(double value)    {return twrite(buffer);}
    std::streamsize write(const std::string &value) {return twrite(buffer.c_str(), value.size());}

};

Comments

Is it alright to make DataStreamInterface an abstract class instead of an interface so I can use the templates like this ?

An abstract class is an interface. The difference is terminology.

Will it affect performance or memory consumption ?

Sure. But not in any meaningful way. But before I can give a more exact answer I need you to be much more specific.

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.

Not a surprise. But nothing to do with it being a C++ function. If you ask the processor do do something it can not do anything else until it finishes. So it will freeze.

Unless you explicitly make your code threaded and do some work on different threads.

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.

Sure. But its not going to help your stall processes by itself.

\$\endgroup\$
  • \$\begingroup\$ Original interface could use dedicated methods to read/write particular data types. In particular one could have implemented textual stream where data is read/write in human readable form. With your approach such implementations are no longer possible. \$\endgroup\$ – Adam Badura Aug 6 '15 at 11:04
  • \$\begingroup\$ I'm not sure whether Axel Isouard wanted to have such text based implementations possible. But mentioning HttpDataStream makes me think so. \$\endgroup\$ – Adam Badura Aug 6 '15 at 11:07
  • \$\begingroup\$ Thanks both of you for your precious advices, I've rewritten the interface and made it an abstract class instead, now the MemoryDataStream has 6 methods, such as length, seek, tell, vread, vpeek, vwrite. The next step would be creating an Observer for that class since I'm sure that Java can just create a separate thread and leave the read method very quickly, then call onReadSuccess or onReadFailure when it finishes. \$\endgroup\$ – Axel Isouard Aug 9 '15 at 10:35
0
\$\begingroup\$

CRTP can also be another design if you don't want to deal with virtual functions for whatever reason..

template<typename T>
class DataStreamInterface
{
    public:
        void read(double* buffer)
        {
            static_cast<T*>(this)->StreamRead(buffer, sizeof(double));
        }

        void write(double* buffer)
        {
            static_cast<T*>(this)->StreamWrite(buffer, sizeof(double));
        }
};

class Stream : public DataStreamInterface<Stream>
{
    public:
        void StreamWrite(void* buffer, std::size_t size) {}
        void StreamRead(void* buffer, std::size_t size){}
};
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.