Why Don't Std::Vector's Elements Need a Default Constructor

Why do I need to have a default constructor in QVectorMyClass?

The reason why std::vector works differently lies in the fact that in vector, raw uninitialized memory is allocated and then calls copy constructor to do the copy whenever required. This process doesn't require calling default constructor for resize(). That's why there is no dependency as such on default constructor.

For more info, See AnT's answer here.

QVector requires type to be default constructible because of the way the internal function realloc() is implemented.

Source: Understanding the Qt containers

Is there still a need to provide default constructors to use STL containers?

This quote is from the C++ Programming Language, Special edition , 2005 by Bjarne Stroustrup in section 16.3.4:

If a type does not have a default constructor, it is not possible to create a vector with elements of that type, without explicitly providing the value of each element.

So it was indeed a standard requirement. It was also required that (section 17.1.4) :

To be an element of a container, an object must be of a type that allows the container implementation to copy it. The container may copy it using a copy constructor or an assignment; in either case the result of the copy must be an equivalent object.

So yes, there were "official" constructor requirementsand the library implementation were supposed to be interchangeable and not add other requirements. (Already in the very first proposal for STL in 1995, the authors tried as much as possible to clearly indicate specifications and narrow down the implementation dependent flexibility.)

You therefore had to provide a default constructor in the case where you declared other constructors:

If a user has declared a default constructor, that one will be used; otherwise, the compiler will try to generate one if needed and if the user hasn't declared other constructors.

Nowadays, this requirement is relaxed. Since C++11:

The requirements that are imposed on the elements depend on the actual operations performed on the container.

So you can define a vector for a class without default constructor, if it doesn't make sense. This for example perfectly works (online demo):

class MyClass {
public:
MyClass(int x) {}
};
int main() {
vector<MyClass> v;
MyClass test{1};
v.push_back(test);
}

But it works only as long as you don't use any operation that would need the default constructor. For instance v.resize(6); would fail to compile.

vector resize seems to require default constructor in C++ = 11

Yes and no.

The overload that you are currently using does.
There is however a second overload where you can supply a value for the new elements - which is needed even if you know that you'll be resizing it to a smaller size.

constexpr void resize( size_type count, const value_type& value );

So either:

v.resize(2, 0);

or erase the elements you do not want to keep. That way, you do not need to supply a value.

v.erase(std::next(v.begin(), 2), v.end());

If you want to use resize() as the main method of resizing you could add a helper function that only uses erase() as a fallback for types that are not default constructible.

#include <type_traits>

template <typename C>
void downsize(C& c, std::size_t size) {
if (size < c.size()) {
if constexpr (std::is_default_constructible_v<typename C::value_type>) {
c.resize(size);
} else {
c.erase(std::next(c.begin(), size), c.end());
}
}
}

Why does std::vector require move-constructors for its elements?

As implied in the comments, there isn't simply one blanket set of requirements on all objects you store in a vector. Rather, the requirements are placed on specific operations.

Although a number of operations do require than the object type be MoveConstructible and/or MoveAssignable, copy construction and copy assignment qualify for meeting that requirement--that is, copy constructible is basically a superset of move constructible and (likewise copy assignment vs. move assignment).

The reverse is not true though. For example, if you use the two argument constructor for a vector:

std::vector<T> vec(n, t);

...then T must be copy constructible (because it's going to attempt to create n copies of t in the vector, and with move construction, it would only be able to create one item).

If you wanted to look at it as an inheritance hierarchy, you could think of copy(assignment|construction) as a derived class, and move(assignment|construction) as the base class, so you can implicitly substitute copying for moving but not vice versa.

There are quite a few cases where requirements on elements of a list are looser than those on elements of a vector or deque (requirements on elements of vector and deque are nearly always identical, the sole exceptions of which I'm aware (thanks @yakk) being emplace_back, which requires MoveConstructible for vector, EmplaceConstructible otherwise and the next item listed below--but note its caveat).

If you use std::vector<x> myvector(i, j); or myvector.assign(i, j);, (where i and j are iterators) then the requirements on T depend on the class of the iterators--if they're forward iterators, then T only needs to be EmplaceConstructible, otherwise T must be MoveConstructible. Caveat: although this does reflect the current wording in the standard, according to LWG 2266 the restrictions for the constructor are incorrect: they should apply regardless of the iterator category, and apply equally to vector and deque. Some future version of the standard will reflect that, but in real use that's how things already are.

In case anybody cares about why that is, and why the special treatment for non-forward iterators: the problem is that with something like an std::istream_iterator, there's no way to know ahead of time how many items an iterator range might refer to. With something like random-access iterators, it can simply use j - i to determine the number of items, make that much space, and insert appropriately. To work efficiently with istream_iterators, they normally copy the data to the end of the current collection, then after the entire range has been copied/moved into the collection, use rotate to move them to the desired location. The extra requirement(s) support using rotate.

There's one variant of insert that requires elements to be swappable in addition to (Move|Copy)(Constructible|Assignable) if you're doing the insert into a vector or deque (the reasoning here is similar to that given above: for this variant of insert, elements are actually inserted in one place, and then moved around to get them to where they belong).

std::vector works with classes that are not default constructible?

The requirement in C++03 is that types being stored in a container be CopyConstructible and Assignable (see §23.1 Container Requirements). However, in C++11 these requirements are relaxed, and tend to apply to the operations performed on the container. So a simple default construction has no requirements (see teble 96, §23.1 in C++11 standard).

As soon as you try to copy a vector, or insert elements into it, you will meet the CopyInsertable, CopyAssignable, EmplaceConstructible, MoveInsertable, MoveAssignable etc. requirements

Initializing a std::vector with default constructor

std::vector has a constructor declared as:

vector(size_type N, const T& x = T());

You can use it to construct the std::vector containing N copies of x. The default value for x is a value initialized T (if T is a class type with a default constructor then value initialization is default construction).

It's straightforward to initialize a std::vector data member using this constructor:

struct S {
std::vector<int> x;
S() : x(15) { }
}

Can the std::vector default constructor throw an exception

It depends on the default constructor of Allocator. The default constructor of std::vector is declared as

vector() noexcept(noexcept(Allocator())); (since C++17)

And if std::allocator is used then it's noexcept(true); i.e. won't throw exceptions.

allocator() noexcept; (since C++11)

Hence, before C++17, or if using a non-default allocator, throwing exceptions is possible.

is default enough for a c'tor d'tor of a class with vector elements only?

You don't need to write your own, but I'd modify the declaration of your constructor.

Item(const std::vector<std::string>& v, const std::vector<std::string>& e):V(v), E(e){}

Always prefer passing large object by const reference to by value. Passing object by value may causes unnecessary copy of object.



Related Topics



Leave a reply



Submit