【问题标题】:Why does std::vector enforce copy on initialization?为什么 std::vector 在初始化时强制复制?
【发布时间】:2017-12-21 22:05:27
【问题描述】:

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

#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 测试。

【问题讨论】:

  • 这是因为使用了std::initializer_list 构造函数。这种类型的语义是不幸的。
  • 本 Q/A 解释了这种情况并提供了一些解决方法:stackoverflow.com/questions/8468774/…
  • @StoryTeller @M.M 我明白了,所以问题不在于std::vector,而在于std::initializer_list,它显然无法转发右值引用?如果有人可以请发布正确的答案,我会接受。
  • 您可以回答您的问题并接受 :) SO 模型甚至鼓励这样做。
  • @StoryTeller 感谢您的提示。我知道我可以回答自己的问题,但在这种情况下,我觉得我会从那些向我指出答案的人那里获得荣誉。无论如何,如果没有人发布答案,我会自己解决问题。

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


【解决方案1】:

这不是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)。

失去额外的As 可能是值得的。

另一种选择:

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 一样高效。

【讨论】:

    猜你喜欢
    • 2016-07-25
    • 1970-01-01
    • 2021-10-26
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    • 2021-06-17
    • 2013-06-06
    • 2020-10-28
    相关资源
    最近更新 更多