Jump to content
Search In
  • More options...
Find results that contain...
Find results in...

ByteStream


JeffSventora
 Share

Recommended Posts

This is a useful class I wrote that you can use to write data to a buffer.

**ByteStream.h**

```

#ifndef _BYTESTREAM_H_

#define _BYTESTREAM_H_

#include using std::strlen;

using std::strcpy;

using std::memcpy;

class ByteStream

{

// Data Members

char* buffer;

int bufferLen;

int bufferSize;

int read;

int write;

// Private Methods

void Resize(void);

public:

// Constructors

ByteStream(void);

ByteStream(int Capacity);

// Destructor

~ByteStream(void);

// Write Functions

template ByteStream& operator<<(T& data)

{

int sizeOf = sizeof(T);

int newSize = sizeOf + bufferLen;

while(newSize > bufferSize) { Resize(); }

memcpy(buffer+write, &data, sizeOf);

bufferLen += sizeOf;

write += sizeOf;

return *this;

}

ByteStream& operator<<(const char* data)

{

int strLen = strlen(data) + 1; // Include null terminator

int newSize = strLen + bufferLen;

while(newSize > bufferSize) { Resize(); }

memcpy(buffer+write, data, strLen);

bufferLen += strLen;

write += strLen;

return *this;

}

void WriteBytes(const char* data, int len);

// Read Functions

template ByteStream& operator>>(T& val)

{

int sizeOf = sizeof(T);

int bounds = sizeOf + read;

if(bounds > bufferSize)

return *this;

memcpy(&val, buffer+read, sizeOf);

read += sizeOf;

return *this;

}

ByteStream& operator>>(char* val)

{

strcpy(val, buffer+read);

read += strlen(val) + 1;

return *this;

}

char* ReadBytes(int len);

// Methods

void ResetRead(void) { read = 0; }

void ResetWrite(void) { write = 0; }

// Accessors

char* Buffer(void) { return buffer; }

int Length(void) { return bufferLen; }

int Size(void) { return bufferSize; }

};

#endif

```

**ByteStream.cpp**

```

#include "ByteStream.h"

#include using namespace std;

// Constructors

ByteStream::ByteStream(void)

{

buffer = new char[1];

bufferLen = 0;

bufferSize = 1;

read = write = 0;

}

ByteStream::ByteStream(int Capacity)

{

buffer = new char[Capacity];

bufferLen = 0;

bufferSize = Capacity;

read = write = 0;

}

// Destructor

ByteStream::~ByteStream(void)

{

if(buffer)

{

delete [] buffer;

buffer = nullptr;

}

}

// Private Methods

void ByteStream::Resize(void)

{

int newSize = bufferSize << 1;

// Only copy data we NEED

char* copy = new char[bufferLen];

memcpy(copy, buffer, bufferLen);

delete [] buffer;

buffer = new char[newSize];

memcpy(buffer, copy, bufferLen);

bufferSize = newSize;

delete [] copy;

copy = nullptr;

}

// Public Methods

void ByteStream::WriteBytes(const char* data, int len)

{

int newSize = len + bufferLen;

while(newSize > bufferSize) { Resize(); }

memcpy(buffer+write, data, len);

bufferLen += len;

write += len;

}

char* ByteStream::ReadBytes(int len)

{

int bounds = len + read;

if(bounds > bufferSize)

return nullptr;

char* val = new char[len];

memcpy(val, buffer+read, len);

read += len;

return val;

}

```

**Example**

```

ByteStream bs;

double x = 1.0;

int y = 100;

bs << "HELLO WORLD" << x << y;

char helloworld[32];

double tx;

int ty;

bs >> helloworld >> tx >> ty;

ByteStream bst;

bst.WriteBytes(bs.Buffer(), bs.Length());

```
Link to comment
Share on other sites

Re-allocating and copying memory are slow operations. Therefore, you'll want to rewrite your resize-method in such a fashion that it can accept the amount of bytes that have to be stored. That way, you won't have to use a loop where you keep re-allocating the buffer until you have found a size that satisfies your demand. Additionally, you want to compute the next power of two, as sizes that are a power of two are favoured when using the heap. Furthermore, being able to compute the next power of two allows you to implement greedy allocation in a straight-forward fashion. In order to compute the next power of two you can use the following function from my library:

```
size_t next_power_of_two(size_t input) {

size_t index;

for (index = 1; index < sizeof(size_t); index <<= 1) [

input |= input >> index;

}

return ++input;

}
```

On top of that you are currently using the C++ allocation operators. These are only useful when you want to allocate objects, as they will invoke the constructors and destructors automagically for you. Otherwise, you will simply want to use malloc, calloc, realloc and free instead:

* realloc allows you to resize buffers more easily (if possible, it will simply extend the current chunk of memory, rather than allocating a new one).
* There is no distinction between single entities and arrays, as is the case with the C++ allocation operators.
* The C++ allocation operators are likely to call malloc and free anyway.

So in the end, your resize method will look like this:

```
bool ByteStream::resize(size_t size) {

char *data = NULL;

size = next_power_of_two(size);

if (this.size == size) {

return true;

}

data = (char *)realloc(size);

if (data == NULL) {

return false;

}

this.data = data;

this.size = size;

return true;

}
```

Additionally, your naming convention is confusing, as you tend to use the same style (capitalised camelcase) for both classes and methods. I'd encourage you to use capitalised camelcase (e.g. FooBar) for classes, and plain camelcase (e.g. fooBar) for methods.

If your goal is to read data from files, or write data to files (or across the wire), then you will have to use stdint.h, as this header will define the following types for you:

* int8_t: signed 8-bit integer.
* int16_t: signed 16-bit integer.
* int32_t: signed 32-bit integer.
* int64_t: signed 64-bit integer.
* uint8_t: unsigned 8-bit integer.
* uint16_t: unsigned 16-bit integer.
* uint32_t: unsigned 32-bit integer.
* uint64_t: unsigned 64-bit integer.

Furthermore, you will have to deal with endianness, as some architectures use big endian (e.g. some POWER-processors) rather than little endian (e.g. all x86-processors).

Additionally, you want to avoid storing floats and doubles, directly. As these are encoded differently per architecture as well. My suggestion here is to encode and decode them as IEEE-754 32-bit and 64-bit floating-point numbers respectively. You will also want to avoid using structures directly, as the compiler may introduce padding to ensure that the processor can efficiently access the members within the structure. Therefore, I encourage you to use the members of the structure instead.

Finally, you don't want to use integers to represent memory sizes, as integers may not be sufficiently large enough to represent every possible memory address. Instead, you'll want to use size_t to represent memory sizes.

Yours faithfully,

S.J.R. van Schaik.
Link to comment
Share on other sites

Thanks for the info but I have a question.

With:

```

data = (char *)realloc(size);

if (data == NULL) {

return false;

}

this.data = data;

```

Wouldn't I still need to copy the data from the buffer? It's not guarenteed that realloc will use the same area of memory, right? But you're right, I definitely didn't write this the best possible way. I'll go ahead and modify it.

Also, you're "next_power_of_two" will not work on numbers that are already powers of two.
Link to comment
Share on other sites

> Wouldn't I still need to copy the data from the buffer? It's not guarenteed that realloc will use the same area of memory, right?

realloc will copy over the memory, if it can't extend the current chunk of memory.

> Also, you're "next_power_of_two" will not work on numbers that are already powers of two.

It will, actually, as it doesn't decrement the input before filling in the right-hand side with ones. However, that is the behaviour of next_strict_power_of_two in my repository (I intended to actually show you the one that does have the same behaviour as you described):

```
size_t cdl_next_power_of_two(size_t input) {

size_t index;

--input;

for (index = 1; index < sizeof(size_t); index <<= 1) {

input |= input >> index;

}

return ++input;

}
```

Yours faithfully,

S.J.R. van Schaik.
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...