Walter
Walter

Reputation: 45414

strange behaviour of std::vector::resize() with gcc 4.7.0

I'm still confused about the behaviour of std::vector::resize(). Consider the following code (see also type requirements for std::vector<type>)

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  A(A const&) { assert(0); }  // is required but doesn't fire in vector::resize
  int X;
};

int main()
{
  std::vector<A> a;
  a.resize(4);        // would not compile without A::A(A const&) or A::A(A&&)
}

Without A::A(A const&) or A::A(A&&), the line with a.resize(4); doesn't compile. However, that constructor is never called: the assert(0) doesn't fire! Can somebody explain that to me?

My interpretation is that the presence of either of these constructors is required by the template magic of allocator_traits<> (used by std::vector::resize()), but is actually never called. However, why would you require the presence of a method if you're not calling it?

Upvotes: 2

Views: 487

Answers (3)

BЈовић
BЈовић

Reputation: 64213

In your example, when you call vector::resize() method, the constructor is called instead of the copy constructor. That is why you do not see assert being triggered.

As for why you need the copy-constructor (and move constructor, which you haven't defined and declared), is that the template types have to be Copy-constructable and move-constructable. [container.requirements.general]/15 defines the requirements for container's type :

— T is DefaultInsertable into X means that the following expression is well-formed: allocator_traits<A>::construct(m, p);
— An element of X is default-inserted if it is initialized by evaluation of the expression allocator_traits<A>::construct(m, p);
where p is the address of the uninitialized storage for the element allocated within X.
— T is CopyInsertable into X means that the following expression is well-formed: allocator_traits<A>::construct(m, p, v);
— T is MoveInsertable into X means that the following expression is well-formed: allocator_traits<A>::construct(m, p, rv);
— T is EmplaceConstructible into X from args , for zero or more arguments args, means that the following expression is well-formed: allocator_traits<A>::construct(m, p, args);
— T is Erasable from X means that the following expression is well-formed: allocator_traits<A>::destroy(m, p);

Upvotes: 0

ecatmur
ecatmur

Reputation: 157324

The latest revision of the standard (n3376) says:

12 - If size() < sz, appends sz - size() default-inserted elements to the sequence.
13 - Requires: T shall be MoveInsertable and DefaultInsertable into *this.

The implication is that MoveInsertable is required for any reallocation that might occur, while DefaultInsertable is required for the actual appending. So your copy or move constructor will fire only if your vector already contains elements and needs to be reallocated.

Indeed, if we write:

std::vector<A> a;
a.resize(1);
assert(!a.empty() && a.capacity() < 4);
a.resize(4);

then the copy- or move-constructor of A is called, and your assert is triggered.

Upvotes: 4

David Schwartz
David Schwartz

Reputation: 182753

In order to resize a vector, existing elements must be placed into the newly-allocated chunk of memory if the vector didn't have enough space to hold the elements required by the new size. This is done by copy-constructing them. So you must have a copy constructor to resize a vector. In this case, there are no existing elements, so the copy constructor is not called. But it still must be present.

Upvotes: 3

Related Questions