使用初始化列表的 vector<unique_ptr<A> >

7

我遇到了一个错误:在编译类似下面代码时,“std::__1::unique_ptr >”的不显式删除拷贝构造函数被调用。使用c++ -std=c++14 unique_ptr_vector.cpp -o main命令编译时出现。

这是一个简化版:

头文件 'my_header.h':

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

class A{
public:
    A() : n(0) {}
    A(int val) : n(val) {} 
    A(const A &rhs): n(rhs.n) {}
    A(A &&rhs) : n(std::move(rhs.n)) {}
    A& operator=(const A &rhs) { n = rhs.n; return *this; }
    A& operator=(A &&rhs) { n = std::move(rhs.n); return *this; }
    ~A() {}

    void print() const { std::cout << "class A: " << n << std::endl; }
private:
    int n;
};

namespace {
    std::vector<std::unique_ptr<A>> vecA = {
        std::make_unique<A>(1),
        std::make_unique<A>(2),
        std::make_unique<A>(3),
        std::make_unique<A>(4)
    };
}

我的源文件是unique_ptr_vector.cpp:


#include "my_header.h"

using namespace std;

int main()
{
    for(const auto &ptrA : vecA){
        ptrA->print();
    }
    return 0;
}

我需要为每个组件单独使用push_back(std::make_unique<A>(<some_number>))吗?还是有更好的方法在头文件中填充容器?总的来说,这是一个不好的主意吗?
我看到过一些问题,比如这个这个这个
我知道现在似乎不可能使用初始化列表。但是人们通常如何处理container<unique_ptr>?我应该避免在头文件中初始化它吗?

2
问题不在于删除器,而在于 std::unique_ptr 是移动语义的,而 std::initializer_list 构造函数会复制元素。恐怕你只能使用 .push_back 方法了。 - DeiDei
我考虑这种可能性是因为在头文件中初始化一个 container<unique_ptr> 对我来说更容易。否则,我将不得不定义另一个函数来填充该容器。这没问题,我对 push_back() 没有任何问题,但我认为应该有一种更简洁的方法。 - Chen
1个回答

19

初始化列表是对const数组的包装。

const类型的unique_ptr不能被移动。

我们可以像这样合法地绕过这个问题:

template<class T>
struct movable_il {
  mutable T t;
  operator T() const&& { return std::move(t); }
  movable_il( T&& in ): t(std::move(in)) {}
};

template<class T, class A=std::allocator<T>>
std::vector<T,A> vector_from_il( std::initializer_list< movable_il<T> > il ) {
  std::vector<T,A> r( std::make_move_iterator(il.begin()), std::make_move_iterator(il.end()) );
  return r;
}

示例链接

使用:

auto v = vector_from_il< std::unique_ptr<int> >({
  std::make_unique<int>(7), 
  std::make_unique<int>(3)
});

如果你想知道为什么初始化列表引用const数据,那么你需要查阅委员会记录或询问在场的人。我猜这是关于最小惊讶原则和/或对可变数据和视图类型(例如将array_view重命名为span)有恐惧症的人的问题。
如果你想要更多不仅仅是向量:
template<class C, class T=typename C::value_type>
C container_from_il( std::initializer_list< movable_il<T> > il ) {
  C r( std::make_move_iterator(il.begin()), std::make_move_iterator(il.end()) );
  return r;
}

需要进行调整才能与关联容器正常工作,因为我们还想移动键。

template<class VT>
struct fix_vt {
  using type=VT;
};
template<class VT>
using fix_vt_t = typename fix_vt<VT>::type;
template<class VT>
struct fix_vt<const VT>:fix_vt<VT>{};
template<class K, class V>
struct fix_vt<std::pair<K,V>>{
  using type=std::pair<
    typename std::remove_cv<K>::type,
    typename std::remove_cv<V>::type
  >;
};

template<class C, class T=fix_vt_t<typename C::value_type>>
C container_from_il( std::initializer_list< movable_il<T> > il ) {
  C r( std::make_move_iterator(il.begin()), std::make_move_iterator(il.end()) );
  return r;
}

刚刚碰巧看到这个,太棒了!有关于地图的提示吗?我需要的正是留给读者的练习 :-D(“仍需要调整才能正确使用关联容器,因为我们还想移动键”)。 - Sergio Losilla
1
@SergioLosilla 在推导 T 时,您需要从 std::map<K,V>::value_type 的键部分中删除 const,以便移动操作可以(高效地)工作。 - Yakk - Adam Nevraumont
我在这里学到了很多,谢谢 :-)如果您不介意,我会继续提问。使用using iptr = std::unique_ptr<int>;以下代码可以正常工作vector_from_il<iptr>({iptr{new int{3}}, iptr{new int{5}}});但是以下代码无法正常工作,因为模板推导失败(我正在使用带有-std=c++14的g++ 8.1.1):container_from_il<std::vector<iptr>>({iptr{new int{6}}, iptr{new int{9}}}); - Sergio Losilla
我添加了一个问题来跟进这个问题,这样我也可以骚扰其他人 :-) https://stackoverflow.com/questions/50629016/initializing-container-of-unique-ptrs-using-initialization-list-continued - Sergio Losilla
我为涉及多态性的相关案例添加了一个后续问题,https://dev59.com/m7z4oIgBc1ULPQZFvsd6 - JW Peterson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接