developer tip

이동 전용 유형의 벡터를 나열 초기화 할 수 있습니까?

optionbox 2020. 9. 8. 07:53
반응형

이동 전용 유형의 벡터를 나열 초기화 할 수 있습니까?


GCC 4.7 스냅 샷을 통해 다음 코드를 전달하면 unique_ptrs를 벡터 에 복사하려고합니다 .

#include <vector>
#include <memory>

int main() {
    using move_only = std::unique_ptr<int>;
    std::vector<move_only> v { move_only(), move_only(), move_only() };
}

분명히 std::unique_ptr복사 할 수 없기 때문에 작동 하지 않습니다.

오류 : 삭제 된 함수 'std :: unique_ptr <_Tp, _Dp> :: unique_ptr (const std :: unique_ptr <_Tp, _Dp> &) [with _Tp = int; _Dp = std :: default_delete; std :: unique_ptr <_Tp, _Dp> = std :: unique_ptr] '

이니셜 라이저 목록에서 포인터를 복사하려고 할 때 GCC가 올바른가요?


<initializer_list>18.9 의 개요 는 이니셜 라이저 목록의 요소가 항상 상수 참조를 통해 전달된다는 것을 합리적으로 명확하게합니다. 불행히도 현재 언어 개정판의 이니셜 라이저 목록 요소에서 이동 의미를 사용하는 방법이없는 것 같습니다.

구체적으로 다음과 같습니다.

typedef const E& reference;
typedef const E& const_reference;

typedef const E* iterator;
typedef const E* const_iterator;

const E* begin() const noexcept; // first element
const E* end() const noexcept; // one past the last element

편집 : @Johannes는 최고의 솔루션을 답변으로 게시하고 싶지 않은 것 같으므로 그냥 할 것입니다.

#include <iterator>
#include <vector>
#include <memory>

int main(){
  using move_only = std::unique_ptr<int>;
  move_only init[] = { move_only(), move_only(), move_only() };
  std::vector<move_only> v{std::make_move_iterator(std::begin(init)),
      std::make_move_iterator(std::end(init))};
}

에서 반환 된 반복기 std::make_move_iterator는 역 참조 될 때 pointed-to 요소를 이동합니다.


원래 답변 : 우리는 여기에 작은 도우미 유형을 활용할 것입니다.

#include <utility>
#include <type_traits>

template<class T>
struct rref_wrapper
{ // CAUTION - very volatile, use with care
  explicit rref_wrapper(T&& v)
    : _val(std::move(v)) {}

  explicit operator T() const{
    return T{ std::move(_val) };
  }

private:
  T&& _val;
};

// only usable on temporaries
template<class T>
typename std::enable_if<
  !std::is_lvalue_reference<T>::value,
  rref_wrapper<T>
>::type rref(T&& v){
  return rref_wrapper<T>(std::move(v));
}

// lvalue reference can go away
template<class T>
void rref(T&) = delete;

안타깝게도 여기에있는 간단한 코드는 작동하지 않습니다.

std::vector<move_only> v{ rref(move_only()), rref(move_only()), rref(move_only()) };

어떤 이유로 든 표준은 다음과 같은 변환 복사 생성자를 정의하지 않습니다.

// in class initializer_list
template<class U>
initializer_list(initializer_list<U> const& other);

initializer_list<rref_wrapper<move_only>>중괄호-INIT-목록 (만든이 {...})가 변환되지 않습니다 initializer_list<move_only>(가) 있다고 vector<move_only>합니다. 따라서 여기에 2 단계 초기화가 필요합니다.

std::initializer_list<rref_wrapper<move_only>> il{ rref(move_only()),
                                                   rref(move_only()),
                                                   rref(move_only()) };
std::vector<move_only> v(il.begin(), il.end());

As mentioned in other answers, the behaviour of std::initializer_list is to hold objects by value and not allow moving out, so this is not possible. Here is one possible workaround, using a function call where the initializers are given as variadic arguments:

#include <vector>
#include <memory>

struct Foo
{
    std::unique_ptr<int> u;
    int x;
    Foo(int x = 0): x(x) {}
};

template<typename V>        // recursion-ender
void multi_emplace(std::vector<V> &vec) {}

template<typename V, typename T1, typename... Types>
void multi_emplace(std::vector<V> &vec, T1&& t1, Types&&... args)
{
    vec.emplace_back( std::move(t1) );
    multi_emplace(vec, args...);
}

int main()
{
    std::vector<Foo> foos;
    multi_emplace(foos, 1, 2, 3, 4, 5);
    multi_emplace(foos, Foo{}, Foo{});
}

Unfortunately multi_emplace(foos, {}); fails as it cannot deduce the type for {}, so for objects to be default-constructed you have to repeat the class name. (or use vector::resize)


Using Johannes Schaub's trick of std::make_move_iterator() with std::experimental::make_array(), you can use a helper function:

#include <memory>
#include <type_traits>
#include <vector>
#include <experimental/array>

struct X {};

template<class T, std::size_t N>
auto make_vector( std::array<T,N>&& a )
    -> std::vector<T>
{
    return { std::make_move_iterator(std::begin(a)), std::make_move_iterator(std::end(a)) };
}

template<class... T>
auto make_vector( T&& ... t )
    -> std::vector<typename std::common_type<T...>::type>
{
    return make_vector( std::experimental::make_array( std::forward<T>(t)... ) );
}

int main()
{
    using UX = std::unique_ptr<X>;
    const auto a  = std::experimental::make_array( UX{}, UX{}, UX{} ); // Ok
    const auto v0 = make_vector( UX{}, UX{}, UX{} );                   // Ok
    //const auto v1 = std::vector< UX >{ UX{}, UX{}, UX{} };           // !! Error !!
}

See it live on Coliru.

Perhaps someone can leverage std::make_array()'s trickery to allow make_vector() to do its thing directly, but I did not see how (more accurately, I tried what I thought should work, failed, and moved on). In any case, the compiler should be able to inline the array to vector transformation, as Clang does with O2 on GodBolt.


As it has been pointed out, it is not possible to initialize a vector of move-only type with an initializer list. The solution originally proposed by @Johannes works fine, but I have another idea... What if we don't create a temporary array and then move elements from there into the vector, but use placement new to initialize this array already in place of the vector's memory block?

Here's my function to initialize a vector of unique_ptr's using an argument pack:

#include <iostream>
#include <vector>
#include <make_unique.h>  /// @see http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding

template <typename T, typename... Items>
inline std::vector<std::unique_ptr<T>> make_vector_of_unique(Items&&... items) {
    typedef std::unique_ptr<T> value_type;

    // Allocate memory for all items
    std::vector<value_type> result(sizeof...(Items));

    // Initialize the array in place of allocated memory
    new (result.data()) value_type[sizeof...(Items)] {
        make_unique<typename std::remove_reference<Items>::type>(std::forward<Items>(items))...
    };
    return result;
}

int main(int, char**)
{
    auto testVector = make_vector_of_unique<int>(1,2,3);
    for (auto const &item : testVector) {
        std::cout << *item << std::endl;
    }
}

참고URL : https://stackoverflow.com/questions/8468774/can-i-list-initialize-a-vector-of-move-only-type

반응형