Home > Enterprise >  Why std::vector<T>.resize() requires T has default ctor(with no parameter)?
Why std::vector<T>.resize() requires T has default ctor(with no parameter)?

Time:06-29

I've got test code snippet:

#include<vector>
using namespace std;
struct My {
    My(int i) {}
    My(My&&) noexcept {}
    My(const My&) {}
};
int main() {
    vector<My> vm;
    vm.emplace_back(My(3));
    vm.resize(3); // compile error
    return 0;
}

g compile with error:

In file included from /usr/include/c  /9/vector:65,
                 from defaultCtor.cpp:1:
/usr/include/c  /9/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = My; _Args = {}]’:
/usr/include/c  /9/bits/stl_uninitialized.h:545:18:   required from ‘static _ForwardIterator std::__uninitialized_default_n_1<_TrivialValueType>::__uninit_default_n(_ForwardIterator, _Size) [with _ForwardIterator = My*; _Size = long unsig ned int; bool _TrivialValueType = false]’
/usr/include/c  /9/bits/stl_uninitialized.h:601:20:   required from ‘_ForwardIterator std::__uninitialized_default_n(_ForwardIterator, _Size) [with _ForwardIterator = My*; _Size = long unsigned int]’
/usr/include/c  /9/bits/stl_uninitialized.h:663:44:   required from ‘_ForwardIterator std::__uninitialized_default_n_a(_ForwardIterator, _Size, std::allocator<_Tp>&) [with _ForwardIterator = My*; _Size = long unsigned int; _Tp = My]’
/usr/include/c  /9/bits/vector.tcc:627:35:   required from ‘void std::vector<_Tp, _Alloc>::_M_default_append(std::vector<_Tp, _Alloc>::size_type) [with _Tp = My; _Alloc = std::allocator<My>; std::vector<_Tp, _Alloc>::size_type = long unsi gned int]’
/usr/include/c  /9/bits/stl_vector.h:937:4:   required from ‘void std::vector<_Tp, _Alloc>::resize(std::vector<_Tp, _Alloc>::size_type) [with _Tp = My; _Alloc = std::allocator<My>; std::vector<_Tp, _Alloc>::size_type = long unsigned int]
defaultCtor.cpp:12:16:   required from here
/usr/include/c  /9/bits/stl_construct.h:75:7: error: no matching function for call to ‘My::My()’
   75 |     { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
defaultCtor.cpp:6:5: note: candidate: ‘My::My(const My&)’
    6 |     My(const My&) {}
      |     ^~
defaultCtor.cpp:6:5: note:   candidate expects 1 argument, 0 provided
defaultCtor.cpp:5:5: note: candidate: ‘My::My(My&&)’
    5 |     My(My&&) {}
      |     ^~
defaultCtor.cpp:5:5: note:   candidate expects 1 argument, 0 provided
defaultCtor.cpp:4:5: note: candidate: ‘My::My(int)’
    4 |     My(int i) {}
      |     ^~

If resize() insolves reallocation of memory and moving objects, as I already defined My(My&&): internal implementation of STL shouldn't fail our client code, right?

What's the semantic rule behind this? If resize() requires default ctor, then it actually creates some empty My objects, which might harm client code design: some client code base forbids trivial default ctors, by making it private and =default or even deleted.

So STL resize() function actually breaks this kind of coding rule, any workaround for it?

CodePudding user response:

It is required, otherwise it can not increase a vector size without default values.

Workaround is std::unique_ptr<T>, or std::optional<T>, or use another overloaded std::vector<T>::resize with the second parameter for a default T.

vm.resize(3, My(0));

CodePudding user response:

The rule doesn't break your class semantics -- it actually enforces them. When you resize a vector, any new elements added are default-constructed. Since that is not allowed, then you may not resize a vector this way.

Instead, you can use another overload of resize that accepts a value to initialize new elements with. See reference:

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

This relies on the ability to copy values. You defined a copy constructor already and you just need an assignment operator to complete that picture. The default assignment operator will use the copy constructor, but you must explicitly make it available. In C 11 that's easy. In older language editions, you must implement it explicitly.

So, this will compile:

#include<vector>

using namespace std;

struct My {
    My(int i) {}
    My(My&&) noexcept {}
    My(const My&) {}
    My& operator=(const My&) = default;
};

int main() {
    vector<My> vm;
    vm.emplace_back(My(3));
    vm.resize(3, My(0));
    return 0;
}

In the above, any additional elements will be initialized with a copy of My(0). We have not broken the semantics of your design, because it explicitly allows copies.

It should be mentioned that if you are allowing such values as My(0) (or whatever) to represent some kind of "empty" or "unused" state, then one wonders why you don't simply make the int parameter in the constructor optional (default to some value) in the first place.

My(int i = 0) {}

That effectively means you can default-construct the object and would not run into this problem in the first place.

  • Related