tags:

views:

173

answers:

4

I have a std::vector of values for which I know the maximum size, but the actual size will vary during usage:

void setupBuffer(const size_t maxSize) {
  myVector.reserve(maxSize);
}

void addToBuffer(const Value& v) {
  myVector.push_back(v);

  if (myVector.size() == maxSize) {
    // process data...
    myVector.clear();
  }
}

However, in setupBuffer, I need to obtain a pointer to the start of myVector's data. I'm using a third party library where I must cache this pointer up front for use in a call made during the "process data..." section.

void setupBuffer(const size_t maxSize) {
  myVector.reserve(maxSize);

  cachePtr(&(myVector[0])); // doesn't work, obviously
}

I don't want to resize() the vector up front, as I want to use vector.size() to mean the number of elements added to the vector.

So, is there anyway to obtain the pointer to the vector's buffer after allocation (reserve()) but before it has any elements? I would imagine the buffer exists (and won't move as long as I restrict the number of push_back'd values)....maybe this isn't guaranteed?

+1  A: 

No. You are not allowed to access any element in the fector with an index greater than size. Resize is your only option here.

What you can do is something like:

myvec.resize(SOME_LARGE_VALUE);
myLibrary(&myVec[0]);
myvec.resize(GetSizeUsedByLibrary());

When you resize, elements in the vector are not destroyed, except those which had an index above the new size. Elements below the number set in resize are left alone. Example using std::basic_string, but equally applicable to vector

Billy ONeal
Yes, this is what I'm doing currently:myVector.reserve(maxSize);myVector.resize(1);cachePtr(myVector.clear();Seems to work. Just hoped there was a cleaner way. Thanks.
Ryan
A: 

There are lot of hacks around it. But I recommend you valid way - override allocator, the second template argument:

typedef std::vector<MyItem, MyAllocator> myvector_t;

After it in MyAllocator provide fixed buffer allocation, and pass this allocator instance as arguemnt of vector's constructor

Dewfy
-1 for undefined behavior. The vector is going to default construct elements when it is resized, which will destroy the contents above the vector's old size, even with the allocator.
Billy ONeal
Interesting suggestion, but the allocation isn't really the issue. It's more of an API issue of how to get the pointer out of std::vector...not sure a custom allocator would help w/ that.
Ryan
@Billy ONeal - NO, if the size is well known, then you should allocate buffer once - at constructor of allocator. After it reallocation must be prevented (for example by assertion). All allocation should be performed inside allocator's initial buffer.
Dewfy
@Ryan - the idea that pointer to buffer provided not by vector, but allocator.
Dewfy
@Dewfy: Yes, with the allocator you can be sure the buffer won't move. But when you call `resize` on the vector to fix the size, it's going to default construct the whole range, destroying your buffer.
Billy ONeal
@Billy ONeal per stl documentation of resize when it is shrinked: ** Notice that this function changes the actual content of the vector by inserting or erasing elements from the vector; It does not only change its storage capacity. **. See lso comment by @Neil Butterworth. So allocation large-enough capacity first allows grant fixed elements. That is why your case - is a hack.
Dewfy
@Dewfy: The elements won't *move*, but when resize inserts elements into the vector, all the elements above the former `size` will be overwritten with default values. Your allocator can ensure the buffer doesn't move, but the contents returned by the library are destroyed when he calls `resize` to make the vector consistent.
Billy ONeal
A: 

Did you try front()? Also, you could push_back an element, get the address as you did, and then erase it.

All of this is not guaranteed, but you could read the source of your vector<> to see if it's fine for you if you have to do it. You could also roll your own vector pretty easily that has a pointer to the data and never moves it.

Lou Franco
Not sure how front() is different from [0] in vector, at least based on the documentation. Since it returns a reference it would have to have at least one constructed object.
Ryan
I guess I thought that reserve created it and [0] was complaining about accessing an element that doesn't exist. I was wondering if front() checked the bounds (the docs I looked at didn't say). You could add and erase an element.
Lou Franco
+3  A: 

The vector buffer will not be moved after a call to reserve unless you exceed the reserved capacity. Your problem is getting the pointer to the first element. The obvious answer is to push a single fake entry into the vector, get the pointer to it, and then remove it.

A nicer approach would be if the library accepted a functor rather than a pointer, which it would call when it needed to access the buffer - you could make the functor put off getting the address until the buffer had some real contents. However, I realise you don't have the luxury of rewriting the library.

anon
Yeah, this is what I'm doing. Thanks.
Ryan
Nice idea, `+1` from me. I suppose technically this still is undefined behavior, but in practice it will probably work with every implementation.
sbi
@sbi I don't see why this would be UB - any reference?
anon
It's no more UB than any push_back or resize operation. Which is to say, it's not UB.
Ryan
@sbi: If you get a pointer to the fake element, remove the fake element, and then insert a real element(s), the pointer that you got to the fake element can be safely used to refer to the real element (cf. §3.8/7). Of course, if you try to dereference the pointer between when you remove the fake element and insert the real element(s), then you definitely have undefined behavior.
James McNellis
sbi
@sbi Lots of uses of placement new depend on this kind of stuff. And James's reference explicitly allows it.
anon
@Neil: Ah, I see. I now went and actually read the reference James gave. Sorry for the confusion.
sbi