【问题标题】:Issues trying to construct `std::vector` with initializer list尝试使用初始值设定项列表构造 `std::vector` 的问题
【发布时间】:2017-07-16 03:30:10
【问题描述】:

考虑以下代码:

#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&lt;A&gt;({1}) 调用std::vector 的隐式初始化列表构造函数,然后将向量传递给std::make_unique。为什么不编译?
  • 对于as2std::make_unique 的结果是一个右值。为什么在任何地方都要求提供副本?
  • 有没有比我的as3 更惯用或更短的方法来实现这一点?

编辑:我现在想起了as1 中的错误原因。 Meyers 的 Effective Modern C++ 将第 30 项中的初始化列表作为完美转发的失败案例之一:“将大括号初始化器传递给未声明为 std::initializer_list 的函数模板参数被规定为正如标准所说,是一个“非推断上下文”。”

【问题讨论】:

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


    【解决方案1】:

    as1

    使独特的用途“完美转发”。完美转发是不完善的,并且不能很好地支持初始化列表。

    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 的辅助函数会减少样板。

    【讨论】:

      【解决方案2】:

      问题是std::unique_ptr,而不是std::initializer_liststd::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 在 cmets 中指出的那样,后者是唯一具有可能抛出的构造函数的内存安全解决方案。您应该在所有实际代码中使用后一个示例。

      【讨论】:

      • int 的示例在任何情况下都可以正常工作,因为new int 只能抛出bad_alloc,无论如何您都无法从中恢复。对于构造函数可能抛出的自定义数据类型来说,问题要大得多,因为这样您就会从原始指针的部分初始化向量中泄漏内存。 emplace_back 解决方案是唯一的故障安全解决方案。 (+1)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-03
      • 1970-01-01
      • 1970-01-01
      • 2018-09-17
      • 2020-05-05
      • 1970-01-01
      相关资源
      最近更新 更多