0

Is there a class in boost similar to array which is a POD-like type with an array but provides for a variable number of items in the container. That is, I want to say the array has at most 10 items, but may contain from 0 to 10 items at any given moment.

boost::array unfortunately fixes size() to the constant N, so it can't work as a drop in replacement for a vector. In particular I'd like the readers of the structure not to know it is a static array, they can use begin() and end() like any other container.

Obviously push_back() would have to through an exception if it would grow beyond the capacity.

Something already in boost would be preferred.

NOTE: it must be a POD-like data type. For clarity, the entire array-like object, included the contents (which will themselves by POD-like types) must be POD-like. This is for serialization/copying reasons (and for performance related to that serialization/copying).

By POD-like I mean:

  • has compile-time constant size
  • can be safely memcpy'd

For those who say it can't work or isn't possible. There is nothing ingenious about this. The boost::array is a POD-like type. All it would take is adding one extra field, for the current size, to make that into what I want. I'm looking for an implementation that already exists (and is thus properly tested/maintained).

edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
  • 3
    Why does it have to be a POD type? That requirement seems rather arbitrary to me. Do you need to serialize the array or something? I don't think you can have a dynamically-growing array and have it be POD at the same time. There's a lot of bookkeeping code for efficiently dealing with arrays that can grow in size. Just look in the source code of a decent `std::vector` implementation. – In silico Mar 25 '11 at 08:21
  • I agree with @In silico -- I don't think what you're asking for is possible if you want proper memory management with exception safety. – ildjarn Mar 25 '11 at 08:24
  • boost::array create an array on stack, then if you create something on stack, you can't change its size dynamiclly. – Shuo Mar 25 '11 at 08:47
  • 1
    simple answer to this is is that I've not come across anything like this. The best solution AFAIK is to implement a boost array like container (the interface is pretty trivial) and add the size information as you need. – Nim Mar 25 '11 at 08:50
  • @Shuo, but you could have an extra variable called `cur_size` and use it as a limited but dynamic array. I just want a class that wraps this logic into the class itself (for simplicity) – edA-qa mort-ora-y Mar 25 '11 at 08:51
  • It is very confusing that you say that you want dynamic size. This implies that you what the actual size of the structure to be able to change. I would suggest that you remove the work dynamic from this post. – ltc Mar 25 '11 at 08:51
  • 1
    To everyone who is getting hungup on the dynamic size thing, I think what the OP wants is a fixed sized stack allocated array which tracks how many items have been inserted into it (up to it's max size). This is a fairly straight forward requirement which the boost array does not unfortunately meet (i.e. one has no way of testing how many items have been actually touched in the array) – Nim Mar 25 '11 at 08:52
  • Given this user appears to be going around down-rating every answer no doubt just because he doesn't like them rather than them being incorrect perhaps we should give up trying to help him – CashCow Mar 25 '11 at 08:54
  • @Nim : Unless your hypothetical container is only intended to hold other PODs, then it would need a non-trivial destructor, thus no longer being a POD. – ildjarn Mar 25 '11 at 08:57
  • @CashCow, none of the answers are correct, and I've commented each of them and improved the question. – edA-qa mort-ora-y Mar 25 '11 at 08:58
  • 1
    (except for the POD part) not a duplicate, but related: http://stackoverflow.com/questions/3563591/how-should-a-size-limited-stl-like-container-be-implemented, there is an implemetation in the answer of sbi – stefaanv Mar 25 '11 at 08:58
  • @ildjarn, that is correct, it'd only be useful for holding other POD types. – edA-qa mort-ora-y Mar 25 '11 at 08:58
  • 1
    @edA-qa mort-ora-y: I am not sure that you understand what POD actually means, and that means that your requirements make it impossible to answer as you are asking for something that you don't need. Basically for a type to be a POD, it cannot have any constructor, but for the new member attribute size to keep the invariant of keeping track of the actual number of elements, it has to be initialized, and that requires a constructor. For safety the member should also be private, and that is another reason that will break PODness. If you downvoted the answers, **reconsider** – David Rodríguez - dribeas Mar 25 '11 at 09:01
  • 1
    @ildjarn, yes that is correct - this is very useful when you want to serialize data by a simple memcopy rather than the more complicated traversal of the data and serialize. Of course this brings a whole host of other issues (primarily portability - but hey-ho).. – Nim Mar 25 '11 at 09:02
  • @David, assume I call an `init` function. – edA-qa mort-ora-y Mar 25 '11 at 09:03
  • @Nim, @edA-qa mort-ora-y : Apologies if I was stating the obvious; I only mentioned it because to me that requirement makes the whole thing appear niche bordering on useless. – ildjarn Mar 25 '11 at 09:04
  • @edA-qa mort-ora-y: Why instead of saying *I want it POD* do you not state what is it that you need? Why is it that you are so interested in having it be a POD? – David Rodríguez - dribeas Mar 25 '11 at 09:07
  • @ildjarn, on the contrary, if you're after high performance serialization (to file/network etc.) then it's tough to beat a memcopy, but you have to carefully ensure that everything is stack allocated (which makes things very complicated..) – Nim Mar 25 '11 at 09:08
  • @David, I'd hazard that in the OP's mind, POD == stack allocated, it's a guess though... – Nim Mar 25 '11 at 09:10
  • @Nim : Serialization via memcpy and stack vs. freestore allocations are completely orthogonal issues. I do see the value in pursuing memcpy serialization, but I do not see the value in pursuing a POD container that is restricted to holding other PODs. /shrug – ildjarn Mar 25 '11 at 09:12
  • @David, agreed. I've replaced POD with *POD-like*. @Nim, I hesitate to say stack-allocated since they won't actually be on the stack. – edA-qa mort-ora-y Mar 25 '11 at 09:13
  • @ildjarn, yeah, was going to update my comment - it doesn't matter as long as all the state is in the same block (i.e. internally it doesn't have other dynamically allocated memory)... – Nim Mar 25 '11 at 09:13

6 Answers6

2

The problem is (probably) initialization. How do you initialize the size, if it is a POD? How do you enforce the invariant size() <= max_size?

Technically, it would be easy to define something, but whether it would be useful or safe is another question. Why don't you just use boost::array, and maintain your current size as a separate variable?

-- James Kanze

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 2
    @edA-qa mort-ora-y: A class with a constructor is not a POD. Period. Go look it up in a book if you don't trust anyone. – David Rodríguez - dribeas Mar 25 '11 at 09:03
  • Assume I call an `init` function. – edA-qa mort-ora-y Mar 25 '11 at 09:06
  • Apparently in C++0x the definition of POD is relaxed to include classes with constructors. – edA-qa mort-ora-y Mar 25 '11 at 09:54
  • @edA-qa mort-ora-y: if the question is about C++0x, it's worth mentioning that in the question and tagging it appropriately. C++0x is not (yet) a standard. – Steve Jessop Mar 25 '11 at 10:13
  • @Steve, no, I'm fine being corrected, I changed everything to be POD-like. Just thought it was interesting that they changed the meaning in 0x (I'm not sure it covers what I want for sure though). – edA-qa mort-ora-y Mar 25 '11 at 10:28
  • @edA-qa mort-ora-y: sure, just be aware that your definition of "POD-like" is inherently implementation-dependent. The only way the standard guarantees you can `memcpy` an object is if it's really POD (in C++03, at least). So a non-POD object that's safely copyable on all sensible implementations might not be copyable on some implementation that uses its license to do any bizarre thing it likes with non-POD classes. – Steve Jessop Mar 25 '11 at 10:37
  • @Steve, yes, points raised here led me to ask: http://stackoverflow.com/questions/5430801/c-guarantee-and-name-for-pod-like-data-memcpy-capable – edA-qa mort-ora-y Mar 25 '11 at 10:41
1

You say "it must be a POD data type". Your collection does not need to be POD?

If the data type is POD then std::vector provides the functionality you need and many implementation will optimise copy/move semantics where the data is POD.

Why do you need to replace vector?

To answer the question directly though: I doubt there is such a class in boost as vector (maintaining a size) already achieves the purpose and therefore they would consider no real need for it. boost::array does not have a concept of a different size and capacity.

You could easily write your own wrapper class around vector or boost::array to throw if it has exceeded the capacity you set.

Now if you were going to implement this the most likely approach would be a class that contains a vector or a boost array or a regular array and then implement all your functions. I would say that is intuitive but there may be a more clever solution to overload the allocator instead to internally hold a boost::array and use it to make your vector's "allocations". If your allocator runs out of capacity (because its internal array is full) you throw (should probably be bad_alloc). Users then simply use the class as vector and can call all its functions but you will throw if they try to grow the vector higher than its size.

Due to the nature of vector your allocator not be expected to work as a heap where you free certain objects. You will simply be maintaining a contiguous buffer.

With regards to the POD or nearly POD issue, the actual data in the vector is guaranteed to be a contiguous buffer, and internally your allocator is using boost::array so it will be contiguous. If you want something for serialization then boost::serialize will already work with vectors properly.

The whole collection, container and contents, must be a single block of memory of fixed size: determined at compile-time

Your allocator could look like this:

typename< typename T, size_t SIZE >
class Allocator
{
    struct Data
    {
        size_t num_used;
        T[SIZE] data;
    } d;

public:
    // implement allocator's methods
};

Implement these

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • @CashCow: I think it is because he wants to make sure that a certain maximum size is never exceeded. – Björn Pollex Mar 25 '11 at 08:33
  • He wants some kind of limited size container so you insert with push_back() but it fails when full? – CashCow Mar 25 '11 at 08:35
  • That is how I understand the question: *Obviously `push_back()` would have to through an exception if it would grow beyond the capacity.* – Björn Pollex Mar 25 '11 at 08:36
  • push_back(), insert() or any other function that might grow the collection would need to check against capacity, including a constructor that takes 2 iterators – CashCow Mar 25 '11 at 08:41
  • The whole collection, container and contents, must be a single block of memory of fixed size: determined at compile-time. – edA-qa mort-ora-y Mar 25 '11 at 08:41
  • Do you allocate a whole block at a time thus never reallocate? If your fixed size is 1MB and the user is using 1KB, have you still pre-allocated that MB? – CashCow Mar 25 '11 at 08:42
  • @edA-qua mort-ora-y: That is a fine requirement: *The whole collection, container and contents, must be a single block of memory of fixed size: determined at compile-time.*, calling that a POD is not. – David Rodríguez - dribeas Mar 25 '11 at 09:04
  • Your requirements should be in your post – CashCow Mar 25 '11 at 15:00
1

The closer thing I know of would be llvm::SmallVector<T,N> however it's slightly more complicated since it uses dynamically allocated storage when the size grows beyond N (it also copies the existing items, since the storage is guaranteed to be contiguous).

I don't think it would be difficult to realize such a container though, especially using boost::array as a backend. A simple wrapper to maintain the size and move the items around (taking inspiration from the code of vector for example) should be sufficient.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
0

I needed something similar, the motivation was performance, as the cache locality is obviously better than in the dynamically allocated vector.

I created very simple custom class to do this for me.

#pragma once
#include <boost/array.hpp>

template<class T, class IndexType, IndexType MaximumSize>
class StaticVector : public boost::array<T, MaximumSize>
{
  IndexType count;
public:
  StaticVector() : count(0) {}
  inline void add(const T& value)
  {
    if (this->full())
      throw std::runtime_error("Can't add (size = %d)\n", MaximumSize);
    (*this)[this->count] = value;
    this->count++;
  }

  inline void addDirty()
  {
    if (this->full())
      throw std::runtime_error("Can't add, container full.");
    this->count++;
  }

  inline void remove(IndexType index)
  {
    if (this->count > 1)
      (*this)[index] = (*this)[count - 1];
    this->count--;
  }

  inline void clear() { this->count = 0; }
  inline IndexType size() const { return this->count; }
  inline bool empty() const { return this->count == 0; }
  inline bool full() const { return this->count == MaximumSize; }
  inline const T& back() const { return (*this)[this->count - 1]; }
  inline T& back() { return (*this)[this->count - 1]; }

  inline typename boost::array<T, MaximumSize>::iterator  end()       { return this->elems + count; }
  inline typename boost::array<T, MaximumSize>::const_iterator  end() const { return this->elems + count; }
  inline typename boost::array<T, MaximumSize>::const_iterator cend() const { return this->elems + count; }
};
kovarex
  • 1,766
  • 18
  • 34
0

POD stand for plain old data. "A Plain Old Data Structure in C++ is an aggregate class that contains only PODS as members, has no user-defined destructor, no user-defined copy assignment operator, and no nonstatic members of pointer-to-member type." Basically by definition anything that can be resized at runtime cannot be POD.

ltc
  • 3,313
  • 1
  • 28
  • 26
  • In general if it is POD it can be safely copied byte by byte, albeit that if your data contains pointers and you move data, your pointers may still be pointing to old locations, so it is not guaranteed to be safe e.g. in C code – CashCow Mar 25 '11 at 08:39
  • I don't say I need dynamic memory allocations. There is no problem with PODs maitaining state information, that is not conflicting with their definition. – edA-qa mort-ora-y Mar 25 '11 at 08:39
0

One other alternative (if you cannot be bothered to RYO) is to use boost::optional<T> for the array, for example:

boost::array<boost::optional<int>, 10>

The advantage of the above is that you can test each entry to see if it has been set or not rather than using some value state for uninitialized. This should still allow a direct memcopy of the object (whether it's stack or dynamically allocated).

Nim
  • 33,299
  • 2
  • 62
  • 101
  • `sizeof(boost::optional)` > `sizeof(int)`, and `boost::optional<>` is not a POD type, so I don't see how this could allow memcpy serialization. – ildjarn Mar 25 '11 at 09:59
  • @ildjarn, size doesn't mean much in this context, nor does the fact that `boost::optional` is not a POD, what is important is that `boost::optional` has no additional dynamic memory allocation internally (or virtual functions etc.), hence it's safe to copy this way. – Nim Mar 25 '11 at 10:14