type requirements for std::vector

2019-01-28 15:10发布

问题:

I am still confused about the requirements for a type to be used with a std::vector in C++11, but this may be caused by a buggy compiler (gcc 4.7.0). This code:

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  int X;
};

int main()
{
  std::vector<A> a;
  a.resize(4);
}

works fine and produces the expected output, indicating that the default ctor (explicitly given) is called (and not an implicit copy ctor). However, if I add a deleted copy ctor to the class, viz

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  A(A const&) = delete;
  int X;
};

gcc 4.7.0 does not compile, but tries to use the deleted ctor. Is that correct behaviour or a bug? If the former, how to get the code working?

回答1:

The C++11 standard does indeed require CopyInsertable as others have pointed out. However this is a bug in the C++11 standard. This has since been corrected in N3376 to MoveInsertable and DefaultInsertable.

The vector<T, A>::resize(n) member function requires MoveInsertable and DefaultInsertable. These roughly translate to DefaultConstructible and MoveConstructible when the allocator A uses the default construct definitions.

The following program compiles using clang/libc++:

#include <vector>
#include <iostream>

struct A {
  A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
  A(A&&) = default;
  int X;
};

int main()
{
  std::vector<A> a;
  a.resize(4);
}

and for me prints out:

 A::A(); this=0x7fcd634000e0
 A::A(); this=0x7fcd634000e4
 A::A(); this=0x7fcd634000e8
 A::A(); this=0x7fcd634000ec

If you remove the move constructor above and replace it with a deleted copy constructor, A is no longer MoveInsertable/MoveConstructible as move construction then attempts to use the deleted copy constructor, as correctly demonstrated in the OP's question.



回答2:

In C++11, the requirements depend on the operations performed. In the case of std::vector<T>::resize(), the requirement on T is that it be CopyInsertable into the vector.

From §23.3.6.3

void resize(size_type sz);

....

Requires: T shall be CopyInsertable into *this.



回答3:

On ideone, I see one call to the default constructor. But four objects are being created, the others have to be constructed somehow. Actually, a prototype object is default constructed and then copied four times.

The C++11 Standard (section 23.3.6.3) says that "value initialized" objects will be inserted, but also requires that the type is copyable:

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.
  • Requires: T shall be CopyInsertable into *this.

There's no compiler bug here; it is your code which is wrong.



回答4:

To use a class in a vector it should have a copy constructor/assignment operator or a noexcept move constructor/assignment operator. GCC is quite correct not to compile your example which doesn't have any of these.

How would you except the vector to do anything without being able to copy or move what it contains?

The reason that the first example works is that since you didn't define any copy or move constructors or assignment operators, you get the defaults. In the second example, since you explicitly deleted the copy constructor, you don't get any automatically generated constructors or assignment operators.



回答5:

void resize(size_type) requires CopyInsertable, which means that an allocator should be able to construct-copy the type:

::new((void*)p)A(A());

This means that a copy-constructor is required. You should be able to bypass this with a custom allocator:

struct Allocator: public std::allocator<A> {
  void construct(A *, const A &) { }
};

However libstdc++ does not respect this; see Should (in C++11) std::vector::resize(size_type) work for the default constructible value_type int[4]?