试图用初始化列表构造`std :: vector`的问题

时间:2017-07-16 03:30:10

标签: c++ c++11 vector initializer-list

请考虑以下代码:

#include <memory>
#include <vector>

class A {
public:
  explicit A(std::vector<int> &&v) : v_(std::move(v)) {}

private:
  std::vector<int> v_;
};

int main() {
  // compilation error (no matching call to std::make_unique)
  // compiler output: https://ideone.com/4oKjCS
  std::vector<std::unique_ptr<A>> as1 = {std::make_unique<A>({1}),
                                         std::make_unique<A>({2})};

  // compilation error (requested copy of std::unique_ptr)
  // compiler output: https://ideone.com/5LGPoa
  std::vector<std::unique_ptr<A>> as2 = {
      std::make_unique<A>(std::vector<int>({1})),
      std::make_unique<A>(std::vector<int>({2}))};

  // succeeds
  std::vector<std::unique_ptr<A>> as3;
  as3.push_back(std::make_unique<A>(std::vector<int>({1})));
  as3.push_back(std::make_unique<A>(std::vector<int>({2})));
}
  • 对于as1:我希望std::make_unique<A>({1})调用std::vector的隐式初始化列表构造函数,然后将向量传递给std::make_unique。为什么不编译?
  • 对于as2std::make_unique的结果是右值。为什么要在任何地方要求复制?
  • as3相比,是否有更惯用或更短的方法来完成此工作?

编辑:我现在正在记住as1中出错的原因。迈尔斯&#39; Effective Modern C ++ 在第30项中提到初始化列表作为完美转发的失败案例之一:&#34;将支持的初始化程序传递给未声明为std::initializer_list的函数模板参数正如标准所说,法令规定是一个非推断的背景。&#39;&#34;

2 个答案:

答案 0 :(得分:3)

AS1

独特使用&#34;完美转发&#34;。完美转发是不完美的,并且不能很好地支持初始化列表。

AS2

初始化列表是指向自动存储持续时间const数组的(成对)指针。 const个对象无法移动,而是从中复制。你不能复制独特的ptrs。

AS3

template<class T, class...Ts>
std::vector<T> make_vector(Ts&&...ts){
  std::array<T,sizeof...(ts)> tmp={{std::forward<Ts>(ts)...}};
  std::vsctor<T> r{
    std::make_move_iterator(begin(tmp)),
    std::make_move_iterator(end(tmp))
  };
}

给我们:

auto as4=make_vector<std::unique_ptr<A>>(
  std::make_unique<A>(make_vector<int>(1)),
  std::make_unique<A>(make_vector<int>(2))
);

这可能不太理想,但是一个独特的对象ptr是一个很糟糕的概念。

在更复杂的情况下,直接生成唯一A的辅助函数会减少样板。

答案 1 :(得分:2)

问题是std::unique_ptr,而不是std::initializer_list。来自std::initializer_list的值通过临时缓冲区复制到目标对象。 unique_ptr不可复制。您需要以另一种方式初始化它,可能是通过reserve()/emplace_back()

对不起,我知道这听起来令人生气,但确实有没有好的方法来为此目的使用初始化列表。

以下示例显示了如何使用带有初始化列表的原始指针的临时向量。这个例子并不漂亮,我不推荐它用于任何真正的代码,但是如果你设置在初始化列表上,它将与std::unique_ptr一起使用,并且只要构造函数没有引入内存泄漏不要扔。

#include <memory>
#include <vector>


int main(void)
{
    std::vector<int*> v = {
        new int(1),
        new int(2),
        new int(3),
        new int(4),
    };

    std::vector<std::unique_ptr<int>> v1(v.begin(), v.end());

    return 0;
}

相反,我会推荐更类似于原始示例的内容:使用reserve / emplace_back()。也许更冗长,但目的很明确,而且语法更加惯用。

std::vector<std::unique_ptr<int>> v;
v.reserve(50);
for (size_t i = 0; i < 50; ++i) {
    v.emplace_back(std::make_unique<int>(i));
}

正如Henri在评论中指出的那样,后者是唯一可以抛出构造函数的内存安全解决方案。您应该在所有实际代码中使用后一个示例。