20

I have a vector std::vector<std::vector<ContactPairs>> m_contactPairs;

If I call m_contactPairs.push_back() or any other function that will resize the outermost vector, will the elements inside that vector have to reallocate (the inner elements in this case being std::vector<ContactPairs>), or will the inner vectors just do a shallow copy and keep pointing at the same memory they already own?

I'm using Visual Studio 2010, which is prior C++11, but has some of the functionality as extensions

YakovL
  • 7,557
  • 12
  • 62
  • 102
Anne Quinn
  • 12,609
  • 8
  • 54
  • 101

2 Answers2

16

Short answer: It depends on the standard you are using and the library implementation:

  • In C++98 and C++03 there is no move, hence everything will be deeply copied including a reallocation.
  • In C++11 and C++14 there will be a deep copy including a reallocation, or, if the implementation provides a noexcept on the move constructor of std::vector<ContactPairs>.
  • In the upcoming C++17 the inner vector will be moved and no deep copy is performed.

And here's the reasoning:

  1. The inner vectors type std::vector<ContactPairs> has a move constructor which is noexcept according to the upcoming C++17 standard (as of N4296) and not noexcept according to the C++11 and C++14 standards, section [vector.modifiers]. You can also find this here. However, even C++11 and C++14 compliant implementations may specify noexcept, since implementations may provide stronger guarantees than prescribed by the standard (see C++ Standard 17.6.5.12). Many implemetations do not do that yet though.

  2. The implementation of the std::vector<T>::push_back() is required to guarantee strong exception safety, i. e. if it throws there are no side-effects. (See the C++ standard, section [container.requirements.general] §10 or §11 or here.)

  3. If the new size of the vector on which you call push_back() exceeds its capacity, then memory needs to be allocated for the new spot and the elements need to be copied or moved to the new spot. If moving the elements of the outer vector can fail (no noexcept), then the elements need to be copied in order to implement the strong exception guarantee. In this case, each copy of an inner vector does require an additional allocation. However, if moving is noexcept, then the whole moving-in-a-loop cannot throw and is safe to use to implement the strong exception guarantee.

Implementing std::vector<T> move construction with the noexcept guarantee seems to be a trivial thing for std::vector. I suspect, the standards committee might have been hesitant to put this guarantee into the standard for consistency's sake: For other node-based containers it can be beneficial to have sentinel nodes, which require an allocation even for default construction. Since the moved-from container needs to be valid after the move, there might be an allocation necessary for an std::list move which might throw, for example. Hence, there's no noexcept guarantee for the move constructor of std::list and other node-based standard container types.

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • OT: For an affordable version of the standard (which is somehow not easy to find -- searching on the ANSI web site for C++ yields no results!) see http://webstore.ansi.org/RecordDetail.aspx?sku=INCITS%2FISO%2FIEC+14882-2012 for a $60 copy. Probably worth it if you even only occasionally engage in deliberations like this one.-- I have a hard time though understanding why I am being charged for the result of volunteer work in a public framework. But the price tag of standards traditionally is targeted at the corporate customer. – Peter - Reinstate Monica Jan 27 '15 at 16:59
  • There is some grey zones here... In C++11 and C++14 the library implementation is allowed to add `noexcept`, and a conforming implementation may do a deep copy or a move depending on whether more operations in vectors are marked `noexcept`. Furthermore, the growth strategy, and the number of copies that the vector does internally is not standardized, so the number of copies it did is not a *salient* attribute of the container. There are C++03 implementations of standard libraries that will *move* the buffers without triggering deep copies (Dinkumware, BSL) – David Rodríguez - dribeas Jan 27 '15 at 18:17
  • BTW, not requiring `noexcept` was not for consistency, while most people don't use it, there is an extension point in the `Allocator` type passed to the container. A `std::vector` template cannot in the general case guarantee that the allocator can be moved to the destination location without throwing an exception. Allocators are fun that way, most people don't care and yet have to pay a price ;) – David Rodríguez - dribeas Jan 27 '15 at 18:22
  • In C++11, point 2 is partially wrong. `push_back` only provides the strong exception guarantee _if the contained object doesn't throw an exception while copying/moving_. Still researching C++03. – Mooing Duck Jan 27 '15 at 19:25
  • C++03 23.1.1/12 defines `push_back` as equivalent to `a.insert(a.end(),x)`, and says "If an exception is thrown other than by the copy constructor or assignment operator of T there are no effects.", similar to C++11, but since the vector can't move construct that doesn't gain anything, so you're right about C++03. – Mooing Duck Jan 27 '15 at 19:29
  • @MooingDuck Well, yeah, `push_back()` cannot guarantee *"no effects"* in case the type `T` does not provide a proper implementation of a copy constructor or assignment operator, because there's no way it can do so. These operations may touch global state which has visible side-effects. – Ralph Tandetzky Jan 27 '15 at 19:38
9

In C++03, a std::vector reallocation will copy ("deep copy") each element. That means for your situation, each vector would be copied.

In C++11 or later, a std::vector reallocation will move each element only if the elements have a move constructor that is noexcept.

Visual Studio 2010 lacks noexcept support, so you would still get a deep copy.

Zyx 2000
  • 1,625
  • 1
  • 12
  • 18
Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • just to make sure, does "copy" mean the inner vectors would have to reallocate, or is it a shallow copy, where the pointer to that data owned by the inner vectors is copied and there's all? – Anne Quinn Jan 27 '15 at 15:53
  • You seem to disagree with @RalphTandetzky about whether vector's move ctor is declared noexcept. When I looked in the standard I could not see it although noexcept seems trivial and is described as an example in TCPPL iirc. Where is that guarentee in the standard? – Peter - Reinstate Monica Jan 27 '15 at 15:57
  • @PeterSchneider I'm researching that now. Yes I do seem to disagree with RalphTandetzky and I'm getting convinced that he is correct. – Drew Dormann Jan 27 '15 at 15:59
  • Or does the noexcept-ness of the inner vector's move ctor -- and hence the realloc-semantics of the outer one -- depend on the existence of a move ctor for ContactPairs? – Peter - Reinstate Monica Jan 27 '15 at 16:01
  • @PeterSchneider simply the *move*-ness of the inner vector would mean that `ContactPairs` would not need to copy *or* move. – Drew Dormann Jan 27 '15 at 16:04
  • 3
    **Note**: `std::vector` move operations are **not** guaranteed to be `noexcept` (libraries may add the noexcept specification, but C++11 and C++14 don't require this), which means that it might or might not *move*. (I would expect libraries to add the specification when they *know* they can, which basically is dependent on the *allocator*) – David Rodríguez - dribeas Jan 27 '15 at 16:18
  • @DavidRodriguez-dribeas Where does it say, that libraries may add a `noexcept` specification? – Ralph Tandetzky Jan 27 '15 at 18:05
  • 3
    @RalphTandetzky: 17.6.5.12 *[...] An implementation may strengthen the exception-specification for a non-virtual function by adding a non-throwing noexcept-specification* – David Rodríguez - dribeas Jan 27 '15 at 18:13