1

As has been discussed previously, to force any standard container to release its heap memory you can just swap with (or assign to) an empty container.

But this does not appear to work for boost::small_vector.

#include <boost/container/small_vector.hpp>
#include <iostream>

typedef boost::container::small_vector<int,4> Vec;

int main()
{
    Vec v;
    std::cout << v.capacity() << "\n";
    for (int i = 0 ; i < 100 ; ++i)
        v.push_back(i);
    std::cout << v.capacity() << "\n";
    Vec().swap(v);
    // v = Vec(); // doesn't work either
    std::cout << v.capacity() << "\n";
}

Output (Boost 1.76 on Linux x86_64):

4
142
142

I know I can call shrink_to_fit. But this is making some of my generic code messier than it should be, in my opinion.

Is this behavior deliberate? If so, why? If not, does it qualify as a bug?

sehe
  • 374,641
  • 47
  • 450
  • 633
Nemo
  • 70,042
  • 10
  • 116
  • 153
  • Without looking at any documentation, isn’t the point of it that it’s on the stack? Or am I way off base? – sweenish Oct 06 '21 at 01:17
  • @sweenish that's `static_vector` – sehe Oct 07 '21 at 14:39
  • I went and looked at [the documentation](https://www.boost.org/doc/libs/1_77_0/doc/html/boost/container/small_vector.html). "It contains some preallocated elements in-place, which can avoid the use of dynamic storage allocation when the actual number of elements is below that preallocated threshold." It seems like a very clear explanation as to why swapping with an "empty" small_vector still has capacity at the least. – sweenish Oct 07 '21 at 14:43
  • @sweenish: It's not that the empty vector has capacity... It's that the capacity did not go back down to 4. Try increasing the number of `push_back` calls and see what happens. – Nemo Oct 07 '21 at 18:16

1 Answers1

2

As everyone knows, to force any standard container to release its heap memory you can just swap with (or assign to) an empty container.

To be super technical, there are no guarantees in the standard that the empty container won't initially have equally large capacity as the old one. As such, you technically cannot deduce from the capacity that allocation hadn't been freed.

I know I can call shrink_to_fit. But this is making some of my generic code messier than it should be, in my opinion.

Edit: As sehe's demo shows, there appears to not be an issue when using std::swap (or boost::swap) in place of member swap.

It shouldn't however be too bad to write a generic shrink_if_has_capacity. I mean, the implementation of that template might be a few potentially messy lines, but its usage would be just as clean, and the intention even clearer than the swap.

Is this behavior deliberate?

The documentation is extremely terse. There is no specification for the behaviour, nor a rationale. As such, the answer may be unknown except to the author.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    There is _"[• Less memory consumption for small objects (and for big objects if shrink_to_fit is used)](https://www.boost.org/doc/libs/1_77_0/doc/html/container/non_standard_containers.html#:~:text=Less%20memory%20consumption%20for%20small%20objects%20(and%20for%20big%20objects%20if%20shrink_to_fit%20is%20used))"_ which can be seen as explicit documentation – sehe Oct 07 '21 at 14:41
  • @sehe The question is about behaviour of swap. – eerorika Oct 07 '21 at 14:52
  • Precisely. OP asks whether the difference vs. swap(empty) is intentional. That doc quote seems to indicate that it is/ (My comment is mainly in response to your last paragraph) – sehe Oct 07 '21 at 14:55
  • @sehe To my understanding, the confusion is about difference between swapping of standard vector versus swapping of small_vector. – eerorika Oct 07 '21 at 14:56
  • Sure. There's more context to the question, but at least the extremely terse Boost docs do let on about _their_ side of the deal. – sehe Oct 07 '21 at 14:57
  • @sehe: This behavior implies that `swap` (and move construct) allocates memory. Is that what you expect for a container? – Nemo Oct 07 '21 at 18:18
  • @Nemo Huh. Can you give an example? I think the behaviour implies that .swap() does not free. That's a different claim than "it allocates memory". I'm willing to bet cakes that that isn't the case (obviously) – sehe Oct 08 '21 at 00:01
  • 1
    @sehe: Try swapping a large small_vector with an empty one. Query the capacity of both just before and after the swap. Like this: https://compiler-explorer.com/z/8PMPKE8eG How do you want to send me cakes? – Nemo Oct 08 '21 at 16:06
  • 1
    @Nemo I extended your demo to be clearer: https://compiler-explorer.com/z/r7cq7en7b May I get a cut of the cake? – eerorika Oct 08 '21 at 16:23
  • Further improved demo: https://compiler-explorer.com/z/1qe31Kv1c – eerorika Oct 08 '21 at 16:31
  • 1
    Surprise https://compiler-explorer.com/z/azGWTYnqb. I think there's a cinch with the difference between default-constructed and moved-from states. I suspect that it **might** be unintended behaviour (bug) caused by complexity surrounding allocator propagation rules? I would say it's a bug, but maybe devs can say something. Perhaps it was "evolved" when default-constructed state resulted in capacity = 4 (because why not) but then when move-semantics entered the game it got weird? – sehe Oct 08 '21 at 17:34
  • Oh. The short summary is: member swap is wonky, but only when swapping with default-constructed "empty", not moved-from "empty". Free swap is fine. – sehe Oct 08 '21 at 17:35
  • Mmm. I seem to falsify my "moved-from empty" hypothesis: https://godbolt.org/z/ecP9bW4vd but confirm that std::swap (mutual move assign) is still okay: https://godbolt.org/z/zMx8K56Pf . Oh you should be advised all my cakes contain UB. It's not advisable to eat that :) – sehe Oct 08 '21 at 17:46
  • Indeed, the allocator propagation tules are causing this branch to be taken: https://github.com/boostorg/container/blob/develop/include/boost/container/vector.hpp#L2554 And it is... kinda sub optimal. I think the propagatability verdict for identical `small_vector_allocator` types should be positive. Filing a bug report. – sehe Oct 08 '21 at 17:58
  • 1
    https://github.com/boostorg/container/issues/197 – sehe Oct 08 '21 at 18:07
  • There was a response from a dev at that issue. I think it was pretty insightful – sehe Oct 30 '21 at 00:48