Search code examples
c++initializationstdvectorunique-ptr

How to create a vector of unique pointers pointing at default constructed objects


Is there a way to create a vector containing N number of std::unique_ptr<T>s? GCC v11.2 shows huge and cryptic error messages so I'm not able to detect the issue.

Here is an MRE of what I was trying to do:

#include <iostream>
#include <vector>
#include <memory>


// a dummy struct
struct Foo
{
    int m_value;
};


int main( )
{
    constexpr auto rowCount { 10uz };
    constexpr auto colCount { 20uz };

    // a 1D vector of pointers, does not compile
    std::vector< std::unique_ptr<Foo> > vec_1D( colCount, std::make_unique<Foo>( ) );

    for ( const auto& ptr : vec_1D )
    {
        std::cout << "Address: " << ptr << " --- value: " << ptr->m_value << '\n';
    }

    // a 2D vector of pointers, does not compile
    std::vector< std::vector< std::unique_ptr<Foo> > >
        matrix( rowCount, std::vector< std::unique_ptr<Foo> >( colCount, std::make_unique<Foo>( ) ) );

}

I think I'm missing something important about std::unique_ptr here. Is this error because of the fact that unique_ptr is not copy-constructible?

If the above method is not possible, then what could be the alternative?


Solution

  • The line:

    std::vector< std::unique_ptr<Foo> > vec_1D( colCount, std::make_unique<Foo>( ) );
    

    makes use of the following vector constructor:

         vector( size_type count,
                 const T& value,
                 const Allocator& alloc = Allocator());
    

    Which receives a given value and copies it to every element of the vector. From cppreference:

    1. Constructs the container with count copies of elements with value value.

    So you are calling std::make_unique<Foo>(), getting a std::unique_ptr<Foo>&& back from that call, and passing it to the std::vector's constructor so that it copies it across. The problem is that that unique pointer is not copyable.


    You could though:

    • create a vector with a given size, and
    • for every element in the vector,
      • create a unique pointer (one at a time), and
      • move assign that unique pointer to the vector's element.

    The example below uses std::generate to fill the vector. Notice that the generator function is returning a std::unique_ptr<Foo>&& that is move assignable to each vector's element.

    [Demo]

    #include <algorithm>  // generate
    #include <iostream>  // cout
    #include <memory>
    #include <vector>
    
    // a dummy struct
    struct Foo
    {
        Foo() : m_value{value++} {}
        static inline int value{};
        int m_value{};
    };
    
    int main( )
    {
        const size_t count{ 20 };
        std::vector<std::unique_ptr<Foo>> v(count);
        std::generate(v.begin(), v.end(), []() { return std::make_unique<Foo>(); });
        for (auto&& up : v) { std::cout << up->m_value << " "; }
    }
    
    // Outputs
    //
    //   0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19