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

发布于 2024-12-20 11:30:51 字数 606 浏览 6 评论 0 原文

如果我通过 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() };
}

显然这是行不通的,因为 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 尝试从初始化列表复制指针是否正确?

If I pass the following code through my GCC 4.7 snapshot, it tries to copy the unique_ptrs into the vector.

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

error: use of deleted function '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]'

Is GCC correct in trying to copy the pointers from the initializer list?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

温暖的光 2024-12-27 11:30:51

编辑:由于@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 返回的迭代器将在取消引用时移动指向的元素。


原始答案:
我们将在这里使用一些辅助类型:

#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{...}) 创建的 ;move_only>> 不会转换为向量采用的initializer_list。所以我们这里需要两步初始化:

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

Edit: Since @Johannes doesn't seem to want to post the best solution as an answer, I'll just do it.

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


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. 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());
暖树树初阳… 2024-12-27 11:30:51

18.9 中 的概要清楚地表明初始化器列表的元素始终通过 const 引用传递。不幸的是,在当前版本的语言中,似乎没有任何方法可以在初始值设定项列表元素中使用移动语义。

具体来说,我们有:

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

The synopsis of <initializer_list> in 18.9 makes it reasonably clear that elements of an initializer list are always passed via 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
拥抱没勇气 2024-12-27 11:30:51

正如其他答案中提到的, std::initializer_list 的行为是按值保存对象并且不允许移出,因此这是不可能的。以下是一种可能的解决方法,即使用函数调用,其中初始值设定项作为可变参数给出:

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

不幸的是 multi_emplace(foos, {}); 失败,因为它无法推导出 {},因此对于默认构造的对象,您必须重复类名。 (或使用vector::resize

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)

最舍不得你 2024-12-27 11:30:51

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 !!
}

Godbolt


对于旧版 C++ 的类似答案:

使用 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 !!
}

参见它位于 Coliru

也许有人可以利用 std::make_array() 的诡计来允许 make_vector() 直接做它的事情,但我没有看到如何(更准确地说,我尝试过我认为应该有效的,失败了,然后继续前进)。无论如何,编译器应该能够内联数组到向量转换,就像 Clang 在 上使用 O2 所做的那样GodBolt

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

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


Similar answer for older C++:

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.

厌味 2024-12-27 11:30:51

为此,我创建了一个小型库

在 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
}

move_iterator 方法不同,这不一定会移动每个元素。 nullptr 直接放入向量中,而不构造中间 std::unique_ptr

这使得它甚至可以与不可移动的类型一起工作:

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

I've made a small library for this purpose.

run on 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. nullptr is emplaced directly into the vector, without constructing an intermediate std::unique_ptr.

This allows it to work even with non-movable types:

std::vector<std::atomic_int> bar = init{1, 2, 3};
仅此而已 2024-12-27 11:30:51

这是我最喜欢的解决方案。

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

有点丑 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;
}

This is the solution I like the most.

C++17 version

#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

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;
}
锦欢 2024-12-27 11:30:51

尝试为我们其他人提供一个简单中肯的答案。

你不能。它坏了。

幸运的是,数组初始值设定项没有被破坏。

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

如果您想使用该数组来初始化 std::vector>,有无数种方法可以做到这一点,其中许多涉及巴洛克式的令人不快的模板元编程,所有这些都可以通过 for 循环来避免。

幸运的是,在很多情况下,您确实更愿意使用 std::vector,使用数组而不是 std::vector 是可行的。

或者,考虑编写一个 custom::static_vector 类,该类在初始值设定项列表中获取 T*,并在其析构函数中删除它们。也不高兴,但您需要接受这样一个事实:std::vector> 不会在合理的时间内或合理的努力下工作。您可以删除任何执行潜在移动的方法(移动和复制构造函数,T&operator[]() &c)。或者,如果您必须(但您可能不会),请尝试并实现基本的移动语义。

参见[1]对此的辩护,为纯粹主义神职人员提供。


[1] 编程语言应该提高生产力。
在这种情况下,模板元编程不会这样做。所有我
想要的是一种确保我不会泄漏分配的内存的方法
静态初始化到堆中,从而使其不可能
使用 valgrind 来验证我没有泄漏内存。

这是一个日常用例。这应该不难。让它变得过于复杂只会导致走捷径。

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.

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.

Alternately, consider writing a custom::static_vector<T> class that take T*'s in an initializer list, and deletes them in its's destructor. 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. You can just delete any methods that do a potential move (move and copy constructors,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] Programming languages are supposed to increase productivity.
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.

That's an everyday use-case. And it shouldn't be difficult. Making it remotely complicated only leads to shortcuts down the road.

百合的盛世恋 2024-12-27 11:30:51

正如已经指出的那样,不可能使用初始值设定项列表来初始化仅移动类型的向量。 @Johannes 最初提出的解决方案工作正常,但我有另一个想法......如果我们不创建临时数组,然后将元素从那里移动到向量中,而是使用放置 new 来,会怎么样?初始化这个数组已经代替向量的内存块了吗?

这是我使用参数包初始化 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;
    }
}

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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文