4

I want to port a Qt C++11 function to standard C++11. The function has a QByteArray parameter that accepts any kind of data (text, binary data, etc.) and calculates a hash from the data.

QByteArray seems well suited because it can carry any kind of data and has a lot of functions that allow filling it from streams, devices, strings, etc. It even can wrap data without doing a deep copy using fromRawData().

Is there a standard C++11 solution that provides similar flexibility? Currently I tend to use good old void* plus size.

Silicomancer
  • 8,604
  • 10
  • 63
  • 130
  • Just about any [STL container](http://en.cppreference.com/w/cpp/container) can store an array of (unsigned) characters. –  Nov 23 '16 at 21:46
  • @Raw N: How can a `vector` be initialized from a pointer and size (or maybe a C string) without doing a deep copy? – Silicomancer Nov 23 '16 at 21:49
  • `myVector.assign(pointer, pointer + size)` maybe? There are also around 7 [different constructors](http://en.cppreference.com/w/cpp/container/vector/vector). –  Nov 23 '16 at 21:55
  • 1
    @Silicomancer If you don't want a deep copy, then use the pointer and the size. You already have them. Most of the standard library operations work over *iterator* ranges, so passing `ptr, ptr+size` as the range is very common. To be more direct, there is nothing remotely close to the breadth of functionality that QByteArray offers. Most of it would require you implementing the behavior. – WhozCraig Nov 23 '16 at 21:56
  • 1
    @RawN ... does a value copy over the range. The specific method the OP mentioned was [fromRawData](http://doc.qt.io/qt-4.8/qbytearray.html#fromRawData), and there is nothing like it that can be used via vector unless you get into some serious std::allocator overloading (and frankly, it wouldn't be worth the trouble). – WhozCraig Nov 23 '16 at 21:59
  • 1
    `std::vector` would seem to me to be the obvious replacement for `QByteArray` if you just want a container that can hold arbitrary binary data.. – Jesper Juhl Nov 23 '16 at 22:03
  • 1
    Hopefully we get `std::array_view` and that would work well in this case. Right now I think you can get it as part of the core guidelines library. – NathanOliver Nov 23 '16 at 23:04

3 Answers3

3

I do not know such kind of standard container that has the same complex functionality what QByteArray has but I would start with std::vector< char > and would implement a wrapper around that with the missing and necessary functions.

std::vector has high flexibility, you can access each element in constant time and you can easily convert it to std::string (for example Converting a vector to string).

If the insert operation is more important maybe you can try std::list< char >. Mainly this is linked list implementation.

Based on the comment from Jesper Juhl: using uint8_t as template argument would describe the real the byte array behavior.

Community
  • 1
  • 1
Tibor Takács
  • 3,535
  • 1
  • 20
  • 23
  • But does it support wrapping data without doing a deep copy? If it doesn't I can't imagine how such a wrapper could do it. – Silicomancer Nov 23 '16 at 21:54
  • With iterators or with operator[] you can access the element without deep copy. – Tibor Takács Nov 23 '16 at 21:57
  • 1
    @TiborTakács He means the ability to mimic the [**fromRawData**](http://doc.qt.io/qt-4.8/qbytearray.html#fromRawData) functionality. – WhozCraig Nov 23 '16 at 22:01
  • Not a very general use case but with special allocatior object you can theoretically do it. http://en.cppreference.com/w/cpp/concept/Allocator/ – Tibor Takács Nov 23 '16 at 22:06
  • 1
    `char` would be a bad template argument for the vector since you don't know if it is signed or unsigned across implementations (there are *3* char types in C++; `char`, `unsigned char` and `signed char` and they are all different types to the type system & the signedness of `char` is implementation defined (yes, this *has* bitten me)). Better be specific and use `uint8_t` in this case (IMHO). – Jesper Juhl Nov 23 '16 at 22:10
  • @Jesper Juhl I could agree, first I also thought to use `uint8_t` but QByteArray uses `char` therefore I thought `char` template argument more suit for the original behavior. – Tibor Takács Nov 23 '16 at 22:16
  • @Tibor Takács I know `QByteArray` uses `char` - I consider that (along with many other things) a failure on the part of the Qt developers. – Jesper Juhl Nov 23 '16 at 22:18
  • @Jesper Juhl: added to the answer. – Tibor Takács Nov 23 '16 at 22:21
3

Is there a standard C++11 solution that provides similar flexibility? Currently I tend to use good old void* plus size.

There's no standard C++11 solution that provides a container that can both manage its own memory or wrap memory managed by someone else.

You could simply copy the QByteArray (it's a few files) and bundle it with your project, if licensing terms allow.

Otherwise, if you only intend to work on containers with contiguous storage for all elements, the const void* and size_t arguments make perfect sense, and will be most portable and adaptable. You can provide convenience overloads as needed. E.g.

HashType calculateHash(const void*, size_t);

template <typename T> HashType calculateHash(const T& container) {
  static_assert(sizeof(typename T::value_type) == 1, "value_type must be byte-sized");
  assert(&container[container.size()-1] == &container[0]+container.size());
  return calculateHash(&container[0], container.size());
}

To support any container, even those with non-contiguous storage, the base calculateHash can take a range and offer an overload for hashing an entire container.

template <typename I>
HashType calculateHash(I start, I const end) {
  HashType hash;
  for (; start != end; ++start)
    hash.update(*start);
  return hash;
}

template <typename C>
HashType calculateHash(const C& container) {
  using std::begin;
  using std::end;
  return calculateHash(begin(container), end(container));
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
2

Not quite standard but you could use the view/span classes from the Guideline Support Library (which should eventually move over into the standard) to emulate fromRawData.

Of course, this does not deal with the lifetime issues that are related to the shared ownership but it might be sufficient in your cases.

Also, while it may not be a requirement for all components, the GSL is advertised as being C++14 based.