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

qqrboqgw  于 2022-12-01  发布在  其他
关注(0)|答案(8)|浏览(207)

如果我通过愚者4.7快照传递以下代码,它会尝试将unique_ptr复制到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() };
}

显然,这是行不通的,因为std::unique_ptr是不可复制的:
错误:使用了已删除的函数'std::unique_ptr〈_Tp,_Dp〉::unique_ptr(const std::unique_ptr〈_Tp,_Dp〉&)[与_Tp = int;_Dp =标准::默认删除;标准::唯一指针〈_Tp,_Dp〉=标准::唯一指针]'
愚者尝试从初始化器列表中复制指针是否正确?

chhqkbe1

chhqkbe11#

**编辑:**由于@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);

由brace-init-list({...})创建的initializer_list<rref_wrapper<move_only>>不会转换为vector<move_only>所采用的initializer_list<move_only>

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

euoag5mw2#

Python 18.9中<initializer_list>的概要相当清楚地表明,初始化器列表的元素总是通过const-reference传递的。不幸的是,在当前的语言版本中,似乎没有任何方法可以在初始化器列表元素中使用move-semantic。
具体而言,我们有:

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
4uqofj5v

4uqofj5v3#

正如在其他答案中提到的,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

ttvkxqim

ttvkxqim4#

  • 针对C20的更新 *:使用Johannes Schaub的std::make_move_iterator()技巧和C20的std::to_array(),你可以使用一个辅助函数,比如unto 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在GodBolt上使用O2所做的那样。

frebpwbc

frebpwbc5#

这是我最喜欢的解决方案。
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;
}
wribegjk

wribegjk6#

这是一个为我们其他人提供简单扼要答案的尝试。
你不能。它坏了。
幸运的是,数组初始值设定项没有被破坏。

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<std::unique_ptr<T>>,有无数种方法可以做到,其中很多都涉及到令人不快的模板元编程,所有这些都可以用for循环来避免。
幸运的是,使用数组而不是std::vector在很多情况下都能正常工作,在这些情况下您确实更愿意使用std::vector。
或者,考虑写一个custom::static_vector<T>类,在初始化器列表中接受T*,并在其析构函数中删除它们。同样不满意,但您需要接受这样一个事实,即std::vector<std::unique_ptr<T>>在合理的时间内或付出合理的努力是无法工作的。您可以删除任何执行潜在移动的方法(move和copy构造函数,T&operator[]() &c)。或者,如果您必须的话,您也可以实现一些基本的move语义(但您可能不需要)。
见[1]的辩护,提供给成员的纯粹主义神职人员。
[1]编程语言应该提高生产率,而模板元编程在这种情况下并没有做到这一点。我所需要的只是一种方法,以确保我不会将静态初始化中分配的内存泄漏到堆中,从而无法使用valgrind来验证我没有泄漏内存。
这是一个日常的用例。而且这应该不难。把它变得很复杂只会带来捷径。

jdg4fx2g

jdg4fx2g7#

为此,我编写了made a small library
在www.example.com上运行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};
acruukt9

acruukt98#

正如前面所指出的,用初始化器列表初始化只移动类型的向量是不可能的。最初由@Johannes提出的解决方案工作得很好,但我有另一个想法...如果我们不创建一个临时数组,然后将元素从那里移动到向量中,而是使用placement 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;
    }
}

相关问题