首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用移动成本低但复制量大的对象初始化容器的首选方法是什么?

使用移动成本低但复制量大的对象初始化容器的首选方法是什么?
EN

Stack Overflow用户
提问于 2016-07-19 07:59:47
回答 3查看 292关注 0票数 7

考虑以下代码:

代码语言:javascript
复制
#include <iostream>
#include <vector>

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
  C(C&&) {std::cout << "A move was made.\n";}
};

std::vector<C> g() {
  std::vector<C> ret {C(), C(), C()};
  return ret;
}

std::vector<C> h() {
  std::vector<C> ret;
  ret.reserve(3);
  ret.push_back(C());
  ret.push_back(C());
  ret.push_back(C());
  return ret;
}

int main() {
  std::cout << "Test g\n";
  std::vector<C> v1 = g();

  std::cout << "Test h\n";
  std::vector<C> v2 = h();
}

使用g++ -std=c++11 main.cpp && ./a.out编译,结果是:

代码语言:javascript
复制
Test g
A copy was made.
A copy was made.
A copy was made.
Test h
A move was made.
A move was made.
A move was made.

请注意,这两个函数都使用复制省略,因此不会复制返回的std::vector<C>

我理解为什么h()使用move-constructor,但是为什么g()使用copy-constructor

来自向量病

(6)初始化程序列表构造函数 以相同的顺序构造带有il中每个元素的副本的容器。

它看起来像初始化程序列表总是复制元素,然后可能意味着initializer-list constructor的性能可能会受到影响,如果C是便宜的移动,但很难复制。

因此,我的问题是:用移动成本低但复制量大的对象初始化容器(例如vector)的首选方法是什么?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-07-19 08:15:53

您可以使用一些样板从初始化程序列表中移动。

代码语言:javascript
复制
template<class T>
struct force_move{
  mutable T t;

  template<class...Args>
  force_move(Args&&...args):
    t(std::forward<Args>(args)...)
  {}
  // todo: code that smartly uses {} if () does not work?

  force_move()=default;
  force_move(force_move const&)=delete;

  template<class U, class...Args>
  force_move(std::initializer_list<U> il, Args&&...args):
    t(il, std::forward<Args>(args)...)
  {}

  operator T()const{ return std::move(t); }
};

template<class T>
struct make_container {
  std::initializer_list<force_move<T>> il;
  make_container( std::initializer_list<force_move<T>> l ):il(l) {}

  template<class C>
  operator C()&&{
    return {il.begin(), il.end()};
  }
};

使用:

代码语言:javascript
复制
std::vector<C> v=make_container<C>{ {}, {} };

这是简洁,有效的,并解决了你的问题。

(可能应该是上面的operator T&&。不太确定,我不太愿意再回一份参考.)

现在,这似乎是个小问题。但是,替代方案太糟糕了。

手动推送/嵌入后的列表很难看,在添加了最大效率的后备要求之后,它变得更加丑陋。天真的il解决方案无法移动。

在我看来,不允许您列出声明实例的元素的解决方案是很尴尬的。您希望能够将内容列表与声明相邻。

另一个“本地列表”选项是创建一个变量函数,它在内部初始化一个std::array (可能是ref ),然后从该数组移动到容器中。然而,这不允许{ {}, {}, {} }样式列表,因此我发现它缺乏。

我们可以这样做:

代码语言:javascript
复制
template<class T, std::size_t N>
std::vector<T> move_from_array( T(&arr)[N] ){
  return {std::make_move_iterator(std::begin(arr)), std::make_move_iterator(std::end(arr))};
}

然后:

代码语言:javascript
复制
C arr[]={{}, {}, {}};
std::vector<C> v = move_from_array(arr);

唯一的缺点是在使用时需要两条语句。但代码并不像我的第一个解决方案那么枯燥。

票数 3
EN

Stack Overflow用户

发布于 2016-07-19 08:22:26

列表 (在Yakk的答案中禁止体操和mutable ),因为initializer_list的元素被声明为const (被认为是危险的名单?)。

我建议在容器中构造具有聚合初始化(即经典数组或std::array )的对象,然后从移动迭代器构造向量。

代码语言:javascript
复制
std::vector<C> h() {
    C[] arr{C(), C(), C()};
    return std::vector<C>(
        std::make_move_iterator(std::begin(arr)),
        std::make_move_iterator(std::end(arr)));
}
票数 3
EN

Stack Overflow用户

发布于 2016-07-19 08:16:16

不幸的是std::initializer_list 不适用于移动语义

如果您需要向构造函数提供参数,我将使用emplace_back,它构造适当的元素:

代码语言:javascript
复制
std::vector<C> h() {
  std::vector<C> ret;
  ret.reserve(3);
  ret.emplace_back(arg1);
  ret.emplace_back(arg2,arg3);
  ret.emplace_back(0,12);
  return ret;
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38452645

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档