简体   繁体   English

我可以列表初始化一个只移动类型的向量吗?

[英]Can I list-initialize a vector of move-only type?

If I pass the following code through my GCC 4.7 snapshot, it tries to copy the unique_ptr s into the vector.如果我通过我的 GCC 4.7 快照传递以下代码,它会尝试将unique_ptr复制到向量中。

#include <vector>
#include <memory>

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

Obviously that cannot work because std::unique_ptr is not copyable:显然这行不通,因为std::unique_ptr不可复制:

error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int;错误:使用已删除的 function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete; _Dp = std::default_delete; std::unique_ptr<_Tp, _Dp> = std::unique_ptr]' std::unique_ptr<_Tp, _Dp> = std::unique_ptr]'

Is GCC correct in trying to copy the pointers from the initializer list? GCC 尝试从初始化列表中复制指针是否正确?

Edit: Since @Johannes doesn't seem to want to post the best solution as an answer, I'll just do it.编辑:由于@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))};
}

The iterators returned by std::make_move_iterator will move the pointed-to element when being dereferenced. std::make_move_iterator返回的迭代器将在被取消引用时移动指向的元素。


Original answer: We're gonna utilize a little helper type here:原始答案:我们将在这里使用一个小助手类型:

#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;

Sadly, the straight-forward code here won't work:可悲的是,这里的直接代码不起作用:

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

Since the standard, for whatever reason, doesn't define a converting copy constructor like this:由于标准,无论出于何种原因,都没有定义这样的转换复制构造函数:

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

The initializer_list<rref_wrapper<move_only>> created by the brace-init-list ( {...} ) won't convert to the initializer_list<move_only> that the vector<move_only> takes.由大括号初始化列表 ( {...} ) 创建的initializer_list<rref_wrapper<move_only>>不会转换为vector<move_only>采用的initializer_list<move_only> So we need a two-step initialization here:所以我们这里需要两步初始化:

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());

The synopsis of <initializer_list> in 18.9 makes it reasonably clear that elements of an initializer list are always passed via const-reference. 18.9 中<initializer_list>的概要清楚地表明,初始化列表的元素始终通过 const-reference 传递。 Unfortunately, there does not appear to be any way of using move-semantic in initializer list elements in the current revision of the language.不幸的是,在当前版本的语言中,似乎没有任何方法可以在初始化列表元素中使用移动语义。

Specifically, we have:具体来说,我们有:

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

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.如其他答案中所述, std::initializer_list的行为是按值保存对象并且不允许移出,因此这是不可能的。 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, {});不幸的是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 ) (或使用vector::resize

Update for C++20 : Using Johannes Schaub's trick of std::make_move_iterator() with C++20's std::to_array() , you can use a helper function like unto make_tuple() etc., here called make_vector() : C++20 更新:使用 Johannes Schaub 的std::make_move_iterator()技巧和 C++20 的std::to_array() ,您可以使用类似于make_tuple()等的辅助函数,这里称为make_vector()

#include <array>
#include <memory>
#include <vector>

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 )
{
    return make_vector( std::to_array({ std::forward<T>(t)... }) );
}

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

See it live on Godbolt .Godbolt上看到它。


Similar answer for older C++:旧 C++ 的类似答案:

Using Johannes Schaub's trick of std::make_move_iterator() with std::experimental::make_array() , you can use a helper function:使用 Johannes Schaub 的std::make_move_iterator()技巧和std::experimental::make_array() ,您可以使用辅助函数:

#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 .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).也许有人可以利用std::make_array()的诡计让make_vector()直接做它的事情,但我没有看到如何(更准确地说,我尝试了我认为应该工作的方法,失败了,然后继续前进)。 In any case, the compiler should be able to inline the array to vector transformation, as Clang does with O2 on GodBolt .在任何情况下,编译器都应该能够内联数组到向量的转换,就像 Clang 对GodBolt上的 O2 所做的那样。

An attempt at a simple to-the-point answer for the rest of us.尝试为我们其他人提供一个简单中肯的答案。

You can't.你不能。 It's broken.它坏了。

Fortunately, array initializers aren't broken.幸运的是,数组初始值设定项没有损坏。

static std::unique_ptr<SerializerBase> X::x_serializers[] = { 
    std::unique_ptr<SerializerBase>{
        new Serializer<X,int>("m_int",&X::m_int)
    },
    std::unique_ptr<SerializerBase>{
        new Serializer<X,double>("m_double",&X::m_double)
    },
  nullptr, // lol. template solutions from hell possible here too.
};

If you then want to use that array to initialize a std::vector<std::unique_ptr<T>> , there are endless ways to do so, many of which involve baroquely unpleasant template metaprogramming, all of which can be avoided with a for loop.如果您随后想使用该数组来初始化std::vector<std::unique_ptr<T>> ,有无数种方法可以这样做,其中许多涉及巴洛克式令人不快的模板元编程,所有这些都可以通过 a for 循环。

Fortunately, using an array instead of a std::vector works in a lot of cases where you really would have preferred to use a std::vector.幸运的是,使用数组而不是 std::vector 可以在很多你真的更喜欢使用 std::vector 的情况下工作。

Alternately, consider writing a custom::static_vector<T> class that take T* 's in an initializer list, and deletes them in its's destructor.或者,考虑编写一个custom::static_vector<T>类,该类将T*放在初始化列表中,并在其析构函数中删除它们。 Also not happy, but you need to resign yourself to the fact that std::vector<std::unique_ptr<T>> isn't going to work in reasonable time or with reasonable effort.也不高兴,但您需要接受这样一个事实,即std::vector<std::unique_ptr<T>>不会在合理的时间或合理的努力下工作。 You can just delete any methods that do a potential move (move and copy constructors, T&operator[]() &c).您可以删除任何可能进行移动的方法(移动和复制构造函数, T&operator[]() &c)。 Or get fancy and implement rudimentary move semantics if you must (but you probably don't).或者,如果必须(但您可能没有),请花哨并实现基本的移动语义。

See [1] for a defense of this, provided for members of the Purist priesthood.参见 [1] 对此的辩护,提供给纯粹主义神职人员。


[1] Programming languages are supposed to increase productivity. [1] 编程语言应该提高生产力。 Template meta-programming isn't doing that in this case.在这种情况下,模板元编程不会这样做。 All I want is a way to ensure that I don't leak memory allocated in static initialization into the heap, thereby making it impossible to use valgrind to verify that I'm not leaking memory.我想要的只是一种确保不会将静态初始化中分配的内存泄漏到堆中的方法,从而无法使用 valgrind 来验证我没有泄漏内存。

That's an everyday use-case.这是一个日常用例。 And it shouldn't be difficult.而且应该不难。 Making it remotely complicated only leads to shortcuts down the road.让它远程复杂化只会导致走捷径。

This is the solution I like the most.这是我最喜欢的解决方案。

C++17 version C++17 版本

#include <vector>
#include <memory>

template <typename T, typename ...Args>
std::vector<T> BuildVectorFromMoveOnlyObjects(Args&&... args) {
  std::vector<T> container;
  container.reserve(sizeof...(Args));
  ((container.emplace_back(std::forward<Args>(args))), ...);
  return container;
}


int main() {
  auto vec = BuildVectorFromMoveOnlyObjects<std::unique_ptr<int>>(
      std::make_unique<int>(10),
      std::make_unique<int>(50));
}

A bit uglier C++11 version丑一点的C++11版本

template <typename T, typename ...Args>
std::vector<T> BuildVectorFromMoveOnlyObjects(Args&&... args) {
  std::vector<T> container;

    using expander = int[];
    (void)expander{0, (void(container.emplace_back(std::forward<Args>(args))), 0)... };

  return container;
}

I've made a small library for this purpose.为此,我制作了一个小型图书馆

run on gcc.godbolt.org 在 gcc.godbolt.org 上运行

#include <better_braces.hpp>

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

int main()
{
    std::vector<std::unique_ptr<int>> foo = init{nullptr, std::make_unique<int>(42)};
    std::cout << foo.at(0) << '\n'; // 0
    std::cout << foo.at(1) << " -> " << *foo.at(1) << '\n'; // 0x602000000010 -> 42
}

Unlike the move_iterator approach, this doesn't necessarily move each element.move_iterator方法不同,这不一定移动每个元素。 nullptr is emplaced directly into the vector, without constructing an intermediate std::unique_ptr . nullptr直接放置在向量中,无需构建中间std::unique_ptr

This allows it to work even with non- movable types:这使得它甚至可以使用不可移动的类型:

std::vector<std::atomic_int> bar = init{1, 2, 3};

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? new最初提出的解决方案工作正常,但我有另一个想法...向量的内存块?

Here's my function to initialize a vector of unique_ptr 's using an argument pack:这是我使用参数包初始化unique_ptr向量的函数:

#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;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 我可以通过元素的完美转发来列表初始化 std::vector 吗? - Can I list-initialize std::vector with perfect forwarding of the elements? 插入只移动类型的向量 - Inserting into a vector of move-only type 我如何列出 - 初始化我自己的类? - how can I list-initialize my own class? std :: move-only类型的列表:无法在VC ++中进入std :: vector - std::list of move-only type: Cannot emplace into std::vector in VC++ 向量<map<move-only type> &gt; 不能用 MSVC 编译</map<move-only> - vector<map<move-only type>> does not compile with MSVC 仅移动类型返回到转换构造函数 - Move-only type returned into converting constructor 在仅移动类型上强制复制(然后销毁) - Force copy (then destroy) on move-only type 仅移动类型的back_inserter - back_inserter for move-only type 包含仅移动类型的类的构造函数是应该通过引用还是通过右值引用来接收仅移动类型? - Should a constructor for a class that contains a move-only type receive the move-only type by reference or by rvalue reference? 括号初始化时出现编译错误 GCC。使用另一个模板化构造函数初始化移动类型向量的实例 - Compilation error with GCC when brace-init.ing an instance of a vector of move-only type with templated constructor with another one
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM