为什么std :: vector在初始化时强制复制?

时间:2017-07-17 11:26:23

标签: c++ c++11 vector move-semantics

我有一个复制/移动探测课程:

#include <iostream>

struct A
{
    A()
    {
        std::cout << "Creating A" << std::endl;
    }

    ~A() noexcept
    {
        std::cout << "Deleting A" << std::endl;
    }

    A(const A &)
    {
        std::cout << "Copying A" << std::endl;
    }

    A(A &&) noexcept
    {
        std::cout << "Moving A" << std::endl;
    }

    A &operator=(const A &)
    {
        std::cout << "Copy-assigning A" << std::endl;
        return *this;
    }

    A &operator=(A &&) noexcept
    {
        std::cout << "Move-assigning A" << std::endl;
        return *this;
    }
};

我发现跑步:

#include <vector>

int main(int, char **)
{
    std::vector<A> v { A() };
}

产生以下输出:

Creating A
Copying A
Deleting A
Deleting A

为什么初始化不会移动对象?我知道std::vector可能会创建undesired copies on resize,但正如您所看到的,添加noexcept在这里没有帮助(此外,我认为调整大小导致副本的原因不适用于初始化)。

如果我改为做以下事情:

std::vector<A> v;
v.push_back(A());

我没有副本。

使用GCC 5.4和Clang 3.8测试。

1 个答案:

答案 0 :(得分:11)

这不是std::vector,而是std::initializer_list

std::initializer_listconst元素数组支持。它不允许非const访问其数据。

这会阻止其数据移动。

但这是C ++,所以我们可以解决这个问题:

template<class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(Args&&...args) {
  std::array<T, sizeof...(Args)> tmp = {{std::forward<Args>(args)...}};
  std::vector<T,A> v{ std::make_move_iterator(tmp.begin()), std::make_move_iterator(tmp.end()) };
  return v;
}

现在我们得到:

auto v = make_vector<A>( A() );

为每个元素提供1次额外移动:

Creating A
Moving A
Moving A
Deleting A
Deleting A
Deleting A

我们可以通过谨慎保留和回避来消除这个额外的实例:

template<class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(Args&&...args) {
  std::vector<T,A> v;
  v.reserve(sizeof...(args));
  using discard=int[];
  (void)discard{0,(void(
    v.emplace_back( std::forward<Args>(args) )
  ),0)...};
  return v;
}

Live example of both - 只需将v2::换成v1::即可查看第一个正在运行的内容。

输出:

Creating A
Moving A
Deleting A
Deleting A

这里可能会有更多的向量开销,因为编译器可能很难证明emplace_back不会导致重新分配(即使我们可以证明它),因此冗余检查将在大多数情况下编译有可能。 (在我看来,如果没有足够的容量,我们需要emplace_back_unsafe即UB。

额外的A s的丢失可能是值得的。

另一种选择:

template<std::size_t N, class T, class A=std::allocator<T>, class...Args>
std::vector<T,A> make_vector(std::array<T, N> elements) {
  std::vector<T,A> v{ std::make_move_iterator(elements.begin()), std::make_move_iterator(elements.end()) };
  return v;
}

用于

auto v = make_vector<1,A>({{ A() }});

您必须手动指定多少元素。它与上面的版本2一样高效。