26

If I have a standard C++ container std::vector<Bar> and I shrink it by calling .resize() with a size smaller than the current .size(), in which order are the extra elements destroyed?

(Implementation choices are interesting if you can find two implementations which differ.)

(This was inspired by a comment from James Kanze.)

Community
  • 1
  • 1
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 3
    huh...interesting question. I guess I'm interested in what situations would it matter? – IdeaHat Apr 04 '14 at 13:14
  • 1
    I'm almost sure that it depends on the implementation. You shouldn't rely on the order of destruction if you want you software be portable. – Jabberwocky Apr 04 '14 at 13:14
  • 6
    @GrahamGriffiths, "try it and see" is hardly ever a good answer. It might tell you how *your* compiler and library work, today, but says nothing about guarantees for other compilers or the future. – Mark Ransom Apr 04 '14 at 13:30
  • but if it's implementation-specific as it seems, then there is no better answer – Graham Griffiths Apr 04 '14 at 13:37
  • 1
    @GrahamGriffiths I think that is part of the question. Is it defined and if so, how? – James Kanze Apr 04 '14 at 13:41

3 Answers3

14

Based on the January 2012 working draft

The January 2012 working draft contains the C++11 standard plus minor editorial changes.

Source, working draft

For vector:

void resize(size_type sz);
Effects: If sz <= size(), equivalent to erase(begin() + sz, end());. If size() < sz, appends sz - size() value-initialized elements to the sequence.

vector::erase does not specify the order of removal. I would expect it to be in order from begin() + sz to end(), because that makes sense to me, but that is just my expectation. I can't find anything about it in the standard.

The implementation of vector distributed with Visual Studio 2013 does appear to indeed erase in that order, as does MinGW's g++ 4.8.1 as well as g++ 4.7.3 (not MinGW). These are the compilers I happen to have easy access to.

In the same standard, for list:

void resize(size_type sz);
1 Effects: If size() < sz, appends sz - size() value-initialized elements to the sequence. If sz <= size(), equivalent to

list<T>::iterator it = begin();
advance(it, sz);
erase(it, end());

and

void resize(size_type sz, const T& c);
Effects:

if (sz > size())
    insert(end(), sz-size(), c);
else if (sz < size()) {
    iterator i = begin();
    advance(i, sz);
    erase(i, end());
}
else
    ; // do nothing

It then goes on to specify absolutely nothing useful about ordering for list::erase.

The implementation of list distributed with Visual Studio 2013 does appear to erase in reverse order, while MinGW's g++ 4.8.1 and g++ 4.7.3 (not MinGW) do not.

Based on the latest working draft at the time of writing

Working draft

For vector

void resize(size_type sz);
Effects: If sz <= size(), equivalent to calling pop_back() size() - sz times. If size() < sz, appends sz - size() default-inserted elements to the sequence.

This indicates that elements are removed in reverse order.

For list:

void resize(size_type sz);
1 Effects: If size() < sz, appends sz - size() value-initialized elements to the sequence. If sz <= size(), equivalent to

list<T>::iterator it = begin();
advance(it, sz);
erase(it, end());

and

void resize(size_type sz, const T& c);
Effects:

if (sz > size())
    insert(end(), sz-size(), c);
else if (sz < size()) {
    iterator i = begin();
    advance(i, sz);
    erase(i, end());
}
else
    ; // do nothing

It then goes on to specify absolutely nothing useful about ordering for list::erase.

For deque the standard specifies the same behavior as for vector.

Bart van Nierop
  • 4,130
  • 2
  • 28
  • 32
  • Can confirm that that VC2013 implementation uses left to right, as you suggested. – ltjax Apr 04 '14 at 13:18
  • 1
    ... **but** your second phrase is not certain enough, references after the point of the erase can be erased in any random sequence per this phrase. – masoud Apr 04 '14 at 13:21
  • I fail to see how the second quote says anything about the order of destruction. – James Kanze Apr 04 '14 at 13:39
  • @MM. You're right. I expect it because it makes sense to me, but I can't find anything in the standard that forces the order. – Bart van Nierop Apr 04 '14 at 13:44
  • @ltjax I can confirm that with VS2013, the order of destruction is different for `resize` than for `erase`: if I insert elements 1, 2, 3 into a list, then do `resize(1)`, the elements are destructed in the order 3, 2; if I do `erase( std::next( l.begin() ), l.end() )`, they are destructed in the order 2, 3. – James Kanze Apr 04 '14 at 14:21
  • The change from `erase` to `pop_back` is interesting, since if the order is defined for `erase`, it changes the order (and if it didn't, it makes the order fixed where it previously wasn't, and breaks most common implementations). For the rest, there is generic text concerning `erase` in the requirements, which can be interpreted to impose order (but can also be interpreted to say nothing about the order, which is my interpretation). – James Kanze Apr 04 '14 at 14:42
  • 1
    @JamesKanze That same concern has been raised by others, as per the issues linked by Rob Kennedy ([issue 2160](http://wg21.cmeerw.net/lwg/issue2160) -- the concert -- and [issue 2033](http://wg21.cmeerw.net/lwg/issue2033) -- the change). – Bart van Nierop Apr 04 '14 at 14:45
  • @JamesKanze I too interpret it as not imposing order. My expectation is based on how I would implement it. – Bart van Nierop Apr 04 '14 at 14:47
  • @JamesKanze I can confirm that for `list`, the order of destruction is different between VS2013 and g++. – Bart van Nierop Apr 04 '14 at 15:11
  • libc++ (clang) erases in reverse order, since quite some time: http://stackoverflow.com/a/11108965/420683 (also in `resize`, if I interpret the source code correctly) – dyp Apr 05 '14 at 08:19
5

With the exception of std::basic_string and std::forward_list, the standard defines resize in terms of erase (in cases where the new size is smaller than the original size), so the question is really: does erase( begin, end ) specify any order of destruction. And all that I can find here is in Table 100, which says that erase( q1, q2 ) "Erases the elements in the range [q1, q2)". Which (to me, at least) still leaves the question open: when the standard uses the notation [q1, q2) (where q1 and q2 are iterators), does it imply in order, or not? A priori, I would think not. At least in the <algorithms> section, it explicitly states when the operations must be in order (and the fact that it explicitly states it in some specific cases sort of suggests that it isn't required in cases where it isn't specified).

For what it's worth: for std::list<>::resize(), g++ calls delete in ascending order; VS in descending order. In the case of VS, this is different than the order of destruction of std::list<>::erase (which may be legal, if the order of destruction is unspecified, and allowed to vary from one call to the next).

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • In some cases it does at least specify linear complexity for `erase(first, last)`, which of course still doesn't give any answer, but at least disallows some crazy (but otherwise possible) orderings. – Bart van Nierop Apr 04 '14 at 14:19
  • @BartvanNierop All that linear complexity really guarantees is that each destructor is only called once (which is hopefully the case anyway). In the standard, complexity is measured in terms of the number of operations provided by the user. – James Kanze Apr 04 '14 at 14:33
3

It depends on the container. For example the effect of applying resize to vectors is the following

12 Effects: If sz <= size(), equivalent to calling pop_back() size() - sz times

So for vectors the order of destroying of elemeents starts from the last element.

For lists the approach is other

Effects: If size() < sz, appends sz - size() default-inserted elements to the sequence. If sz <= size(), equivalent to list::iterator it = begin(); advance(it, sz); erase(it, end());

But there is nothing said in which order the elements are erased.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Looking at the MinGW `vector` source, which I happen to have on this machine, that is not true. It very much removes items from first til last. – Bart van Nierop Apr 04 '14 at 13:26
  • @Bart van Nierop It means that the realization does not satisfy the C++ Standard because I cited the Standard. – Vlad from Moscow Apr 04 '14 at 13:29
  • 3
    So did I. Which begs the question, which standard did you cite? – Bart van Nierop Apr 04 '14 at 13:34
  • @VladfromMoscow How does that not satisfy the standard? – James Kanze Apr 04 '14 at 14:16
  • @Bart van Nierop At present I use Working Draft #3691 – Vlad from Moscow Apr 04 '14 at 14:17
  • @VladfromMoscow The standard says that `resize` is defined in terms of `erase`. So the question remains: does `erase` define the order of destruction? If it does, then the order is defined by the standard. – James Kanze Apr 04 '14 at 14:17
  • @James Kanze In my post there is clear written what the standard says because cited the standard. – Vlad from Moscow Apr 04 '14 at 14:18
  • @JamesKanze @VladfromMoscow The newer standards do indeed say `pop_back()` for `vector`. Not for `list` however. – Bart van Nierop Apr 04 '14 at 14:22
  • @VladfromMoscow Except that the parts of the standard you cite refer to other parts, so we don't know until we've seen the other parts. According to the standard, `resize` must call `erase`, or at least have the same observable behavior as calling `erase`. If `erase` must destruct in a certain order, then `resize` must too. If the order of destruction in `erase` is unspecified, then the implementation could behave as if `erase` used one order when called by the user, and a different order when called from `resize`. – James Kanze Apr 04 '14 at 14:24
  • @BartvanNierop What standard says `pop_back` for `vector`? I'm looking at the official version of C++11, and I don't see anything about `pop_back`; only `erase`. – James Kanze Apr 04 '14 at 14:26
  • 3691 is a draft. It's misleading to refer to it as *the standard*. [Issue 2160](http://wg21.cmeerw.net/lwg/issue2160) points out the ordering side effect that the change ([issue 2033](http://wg21.cmeerw.net/lwg/issue2033)) introduced. Whether that side effect was intended is still an open question. – Rob Kennedy Apr 04 '14 at 14:27
  • @JamesKanze The latest working draft. I unfortunately have no copy of the official standard. I should do something about that. – Bart van Nierop Apr 04 '14 at 14:29
  • @RobKennedy I altered the order of my answer accordingly, as using the latest draft before a standard is quite a bit more solid than using something that is very much a work in progress. Still I should just get myself a copy of the standard. =] Interesting read, btw. – Bart van Nierop Apr 04 '14 at 14:42