5

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 any way 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?

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
Ryan
  • 567
  • 4
  • 15
  • 2
    Why do you need to cache the pointer, rather than safely reading it when you need it? – Mike Seymour Jul 28 '10 at 12:44
  • I'm using a 3rd-party library for I/O to a specific file format. The way it works is to set up 'buffers' in the library by sending a pointer and max size. Then you can asynchronously call e.g. 'write' which flushes the data to disk. I could set up the pointer each time I hit maxSize, but this would be additional overhead (how much I'd have to test). Good point, though. – Ryan Jul 28 '10 at 12:55
  • Note that this question asks how to get the pointer, but answers differ on what you're allowed to *do* with that pointer once you have it. For a question that covers that issue directly, please see [Is accessing the raw pointer after std::vector::reserve safe?](http://stackoverflow.com/questions/8228072/is-accessing-the-raw-pointer-after-stdvectorreserve-safe) – Rob Kennedy Nov 22 '11 at 15:29

4 Answers4

4

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.

  • Nice idea, `+1` from me. I suppose technically this still is undefined behavior, but in practice it will probably work with every implementation. – sbi Jul 28 '10 at 13:01
  • 1
    @sbi I don't see why this would be UB - any reference? –  Jul 28 '10 at 13:03
  • It's no more UB than any push_back or resize operation. Which is to say, it's not UB. – Ryan Jul 28 '10 at 13:07
  • 2
    @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 Jul 28 '10 at 13:20
  • @Neil & @James: I don't know. Is it guaranteed that I can keep a pointer to an object even after the object died? – sbi Jul 28 '10 at 14:08
  • @sbi Lots of uses of placement new depend on this kind of stuff. And James's reference explicitly allows it. –  Jul 28 '10 at 14:18
  • @Neil: Ah, I see. I now went and actually read the reference James gave. Sorry for the confusion. – sbi Jul 28 '10 at 14:30
2

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

Community
  • 1
  • 1
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • Yes, this is what I'm doing currently: myVector.reserve(maxSize); myVector.resize(1); cachePtr(&myVector[0]); myVector.clear(); Seems to work. Just hoped there was a cleaner way. Thanks. – Ryan Jul 28 '10 at 12:48
  • @Ryan: That is invalid. Presumably the C API `cachePtr` accesses past the first element of the vector, which means you've got undefined behavior. For valid behavior, you must stay below `size()`, not `capacity()`. It may seem to work; but a valid answer for undefined behavior is "seems to work". – Billy ONeal Apr 25 '13 at 04:50
0

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
  • 87,846
  • 14
  • 132
  • 192
  • 2
    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 Jul 28 '10 at 12:47
  • 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 Jul 28 '10 at 13:21
-1

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
  • 23,277
  • 13
  • 73
  • 121
  • -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 Jul 28 '10 at 12:41
  • 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 Jul 28 '10 at 12:49
  • @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 Jul 28 '10 at 12:51
  • @Ryan - the idea that pointer to buffer provided not by vector, but allocator. – Dewfy Jul 28 '10 at 12:53
  • @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 Jul 28 '10 at 12:55
  • @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 Jul 28 '10 at 13:09
  • @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 Jul 28 '10 at 14:01