views:

194

answers:

6

So, I've been looking at boost::array but it does require default constructor defined. I think the best way of filling this array with data, would be through a push_back(const T&) method. Calling it more times than SIZE (known at compile-time) would result in assert or exception, depending on build configuration. This way it would always contain meaningful data. Does anyone know efficient, portable, reliable implementation of this concept?

A: 

I don't know any existing implementation, but it should be pretty trivial. Just create a static array and then initialize using placement new to avoid the need for default constructor.

http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10

Edit: Sorry, I totally didn't realize that the compiler can actually start assign the attribute to a non-aligned address. I apologize to everyone.

Let_Me_Be
I guess I'm not so comfortable with low-level C/C++ to understand the dangers of this approach. I do know classes like boost::optional use special "aligned memory" classes to handle storage of objects. But again I'd try to avoid getting dirty with the low-level stuff :)
@user Its seriously very simple. Nothing to be afraid of. Plus it's not low level really. You just create array, manually construct objects when they are pushed and manually destroy them. Its just well, manual :)
Let_Me_Be
@user: `boost::optional` is extremely simple to use, really. However it would mean a slightly buffier object, notably, pointer iteration would have to be done on `boost::optional<T>*` and not `T*`, in case of harsh memory constraints it might not be acceptable, but that's about the simpler solution you can get.
Matthieu M.
@Matthieu: Yeah, I know boost::optional is very simple to use. But instead of wrapping T in boost::optional<T> I would rather like someone implementing my array class concept so that it internally uses the same stack-based aligned memory mechanism as boost::optional class :)
@Let_Me_Be: surely everyone's acting stupid because `p_data` might not be correctly aligned for `T`? If you get it directly from `new` (or another sensibly-specified allocator) then it's aligned for any type that fits into it. But automatic variables and data members don't come with the same guarantee AFAIK.
Steve Jessop
@Steve What? Arrays are guaranteed to be continuous memory blocks of the precise size, no added paddings.
Let_Me_Be
What you're trying to do is create an opaque type (hide the type from the compiler so it doesn't require initializing the elements) There's a very good GOTW article that talks about this exact thing (in a slightly different context) and why it should never be done. http://www.gotw.ca/gotw/028.htm
joshperry
@joshperry @Steve So what you are trying to tell me is that `T x[10];` might be larger then `char x[sizeof(T)*10];`? That can't be true. Plus the Gotw is all about: "Don't do this, cause we have a better way."
Let_Me_Be
@Let_Me_Be: I'm not saying anything about size, I'm talking about alignment. Suppose you're on a platform where `int` must be 4-aligned, and you create a `MyArray<int,1>`. The address of `p_data` (or in general any `char[4]`) is not guaranteed to be a multiple of 4. [Edit: actually, it's fairly likely to be aligned simply because the next data member happens to be an `int`. But it's still not *guaranteed*, and if the plausability that it will work is distracting, suppose `long` has to be 8-aligned, and you create a `MyArray<long,1>`, and it gets an address that is a multiple of 4 but not 8]
Steve Jessop
@Steve I opened new question here: http://stackoverflow.com/questions/3874615/placement-new-issue
Let_Me_Be
@Let_Me_Be: Still, you can overcome the alignment problem: `union {maxlign_t dummy; char rawdata[...];}` where `maxlign_t` is itself a union of various types including long, long double, pointers, member pointers, etc. The compiler should then properly align the char array.
sellibitze
A: 

In C++0x you got std::array<type, size> (probably the same as boost::array). You can initialize array data by using fill() or std::fill_n():

std::array<int, 30> array;
array.fill(0);
boost::array<int, 30> barray;
std::fill_n(barray.begin(), 30, 0);

If you want to get it default-initialized at definition you can use copy-ctor:

static std::array<int, 30> const nullarray = {0, 0, 0, ..., 0}; // nullarray.fill(0);
// (...)
std::array<int, 30> array{nullarray};
erjot
If instead of `int` you use a user-defined type then all 30 will be default constructed (when using `fill`), which is what the OP doesn't want.
joshperry
@joshperry: oh, so that's what `doesn't require default constructor` mean ... ;f So the problem is not with the container - then OP needs to make his default-ctor deleted or private and e.g. use sfinae for default-ctor-check
erjot
A: 

boost::array<T, 12> ta; is no different from T[12] ta;; if you don't use an initializer list then the elements will be default constructed.

The common workaround would be boost::array<T*, 12> ta; or maybe boost::array<unique_ptr<T>, 12> ta;.

The only way to store by value is to copy, no way around that... This is what initializer lists do:

struct A {
    A(int i):_i(i){ cout << "A(int)" << endl; }
    A(const A& a){ cout << "A(const A&)" << endl; }
    ~A(){ cout << "~A()" << endl; }

    int _i;
};

int main(){
    boost::array<A, 2> ta = {{1, 2}};
}

This outputs:

A(int)
A(const A&)
A(int)
A(const A&)
~A()
~A()
~A()
~A()

http://codepad.org/vJgeQWk5

joshperry
No, that's suboptimal for my needs. I want to store few small objects in small arrays by value, not by address.
Using an initializer list might be cumbersome in some scenarios. You can easily change array size by changing compile-time constant in one place but probably you would have to change initializer lists in many other places.
I didn't say it wouldn't be cumbersome, I'm just saying it's the only way to do what you want (barring the opaque type hack recommended in another answer).
joshperry
A: 

Why does it have to reside on the stack? Do you have empirical evidence that creating and reserveing a vector is too slow (using a vector seems like the obvious answer)?

Even if it is, you can create a pool of vectors that have space reserved and swap one of the pre-allocated vectors into a local copy. When you're done with the local one, swap it back again (much like the splice trick for lists).

Mark B
I didn't downvote, but it seems to me that this would have been a great candidate for a comment.
sellibitze
A: 

may be store a boost::variant in your boost::array? make the first parameter an int or something..

i.e.

boost::array<boost::variant<int, foo>, 6> bar;

okay you have to deal with a variant, but it's stack allocated...

Nim
+1, how about boost::optional instead of boost::variant?
sellibitze
It's a solution but far from being optimal. I guess the boost::optional class is an evidence that what I want is doable - probably the boost::optional implementation can be extended to array.
wasn't aware of it till now, just read up on it... neat! thx for the pointer. It's more cleaner than the above...
Nim
@user467799: Sure, it's doable. But you probably have to write such a class yourself. Watch out for alignment issues.
sellibitze
A: 

Well, I would have thought that someone would have brought the answer now, however it seems not, so let's go.

What you are wishing for is something I have myself dreamed of: a boost::optional_array<T,N>.

There are two variants:

  • First: similar to boost::array< boost::optional<T>, N >, that is each element may or may not be set.
  • Second: similar to a std::vector<T> (somehow), that is all beginning elements are set and all following ones are not.

Given the previous questions / comments, it seems you would like the second, but it doesn't really matter as both are quite alike.

template <typename T, size_t N>
class stack_vector
{
public:
  bool empty() const { return mSize == 0; }
  size_t size() const { return mSize; }
  size_t capacity() const { return N; }
  size_t max_size() const { return N; }

  T& operator[](size_t i) { return *(this->pfront() + i); }
  /// ...

private:
  T* pfront() const { return reinterpret_cast<T*>(&mStorage); }

  std::aligned_storage< N * sizeof(T), alignof(T) > mStorage;
  size_t mSize; // indicate how many elements are set, from the beginning
};

Let's focus on those very special operations:

template <typename T, size_t N>
void push_back(T const& t)
{
  new (this->pfront() + mSize) T(t); // in place construction
  ++mSize;
}

template <typename T, size_t N>
void clear()
{
  for (size_t i = 0; i != mSize; ++i)
  {
    (this->pfront() + i)->~T();
  }
  mSize = 0;
}

As you can notice, the main difficulty is to remember that:

  • if no element has been built there yet, you need placement new + copy construction instead of assignment.
  • elements that become "obsolete" (ie would be after the last element) should be properly disposed of (ie their destructor be invoked).

There are many operations on traditional STL container that may be tricky to implement. On a vector, element shuffling (due to insert or erase) are perhaps the most stricking examples.

Also note that with C++0x and initializer-lists vector get emplace_back to directly construct an element in place, thus lifting the CopyConstructible requirement, might be a nice boon dependent on your case.

Matthieu M.
I'm marking it as answered but I really cannot tell for sure whether there may be any potential problems with this approach (besides the obvious std::aligned_storage which may not be supported on all compilers yet).