【问题标题】:vector::push_back and std::move [duplicate]vector::push_back 和 std::move [重复]
【发布时间】:2014-02-18 06:45:52
【问题描述】:

我尝试了以下代码:

#include <iostream>

struct test{
    test(){}
    test(test const &){
        std::cout<<__LINE__<<"\n";
    }
    test(test&&){
        std::cout<<__LINE__<<"\n";
    }
};

#include <vector>
#include <utility> // std::move
int main(){
    auto&& tmp = test();
    std::vector<test> v;
    v.push_back(tmp);
    std::cout<<__LINE__<<"\n";
    v.push_back(std::move(tmp));

    return 0;
}

vs2013 编译器输出:

6 // 复制

18

9 // 移动

9 // 移动

g++ 和 clang++ 输出:

6 // 复制

18

9 // 移动

6 // 复制

我的问题是:

  1. tmp的类型是test&&吗? tmp 是右值吗?

  2. 如果tmp的类型是test&&,为什么第一个push_back没有使用move构造函数?

  3. 最后的输出是从哪里来的?为什么 vs2013 和 g++ 输出的结果不一样?

谢谢。

第三个问题的答案: 它来自 andrew.punnett 评论的重新分配。

【问题讨论】:

  • 我已经修改了我的答案以扩展 Andrew 的评论。
  • 最有效的解决方案是v.emplace_back();,它既不需要复制也不需要移动。
  • 注意:v.emplace_back() 确实会移动对象,如果它是在调用之前构造的
  • @DarkWanderer v.emplace_back(); 在向量内存中直接构造对象。调用之前没有创建对象。
  • @FredOverflow:不。 MyClass instance; v.emplace_back(instance);。虽然我的移动是错误的,但它会通过复制构造函数进行复制。

标签: c++ c++11 move rvalue


【解决方案1】:

tmptest&amp;&amp;的类型吗?

是的,不是的。 tmptest&amp;&amp; 类型的右值引用变量,但作为表达式的标识符tmp 具有test 类型和值类别lvalue&amp; 绝不是表达式类型的一部分。

tmp 是右值吗?

没有。标识符的任何使用都是左值表达式,甚至是右值引用的名称。访问右值引用变量与访问左值引用变量基本相同;只有decltype(tmp) 可以区分。 (通常你会使用decltype((tmp))避免区分。)

如果tmp的类型是test&&,为什么第一个push_back没有使用move构造函数?

因为右值引用的名称仍然是左值。要获取右值表达式,请使用move(tmp)

最后的输出是从哪里来的?为什么 vs2013 和 g++ 输出的结果不一样?

默认情况下,Clang 和 GCC 只为 vector 中的一个对象腾出空间。添加第二个对象时,向量存储被重新分配,导致对象被复制。他们为什么不搬家?因为移动构造函数不是noexcept,所以如果它抛出异常,将无法撤消重新分配。

至于MSVC的两个动作,有两种可能,你可以通过实验来区分——我手头没有副本。

  1. 默认情况下,MSVC 在向量内为两个对象保留了足够的空间。第二步来自内部局部变量。
  2. MSVC 忽略了移动构造函数为noexcept 的要求,并调用它来执行重定位。这将是一个错误,但在这种情况下,它掩盖了一个常见错误。

如果您将vector 替换为deque,您将看不到任何副本,因为不允许deque 假定可复制性。

【讨论】:

  • 大部分是正确的,但是对构造函数的额外调用只是因为向量被重新定位而不是使用“临时内部变量”。例如,在创建向量之后尝试 v.reserve(2)。
  • 这里不是有一些参考折叠诡计吗,即tmp 是 RHS 返回的内容吗?它与整个“通用引用”的东西相同。
  • 所以test&&对test&没有优势?
  • @andrew.punnett 谢谢,我只是假设 GCC 和 Clang 保留了足够的空间。 (test 应该只有一个字节,动态分配少于几个字的价值很少有优势。) Aaand,一个 MSVC 错误看起来很可能。典型的。
  • @user1535111 取决于您所说的“优越性”。右值引用和左值引用之间的最大区别在于它们的初始化方式。尝试替换 auto &amp; 看看会发生什么。
【解决方案2】:

Visual Studio 尚不支持 noexcept 关键字,并且可能不符合 push_back 的异常安全性。此外,额外的输出是增长时容量计算差异的结果。

#include <iostream>
#include <vector>
#include <utility> // std::move

struct Except{
    Except(){}
    Except(Except const &) {
        std::cout<< "COPY\n";
    }
    Except(Except&&)  {
        std::cout<< "MOVE\n";
    }
};

struct NoExcept{
    NoExcept(){}
    NoExcept(NoExcept const &) noexcept {
        std::cout<< "COPY\n";
    }
    NoExcept(NoExcept&&) noexcept {
        std::cout<< "MOVE\n";
    }
};

template <typename T> void Test( char const *title,int reserve = 0) {
    auto&& tmp = T();
    std::cout<< title <<"\n";
    std::vector<T> v;
    v.reserve(reserve);

    std::cout<< "LVALUE REF ";
    v.push_back(tmp);
    std::cout<< "RVALUE REF ";
    v.push_back(std::move(tmp));
    std::cout<< "---\n\n";
}
int main(){
    Test<Except>( "Except class without reserve" );
    Test<Except>( "Except class with reserve", 10 );
    Test<NoExcept>( "NoExcept class without reserve" );
    Test<NoExcept>( "NoExcept class with reserve", 10 );
}

以及clang中的结果:

Except class without reserve
LVALUE REF COPY
RVALUE REF MOVE
COPY
---

Except class with reserve
LVALUE REF COPY
RVALUE REF MOVE
---

NoExcept class without reserve
LVALUE REF COPY
RVALUE REF MOVE
MOVE
---

NoExcept class with reserve
LVALUE REF COPY
RVALUE REF MOVE
---

【讨论】:

    猜你喜欢
    • 2013-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-15
    • 2021-06-18
    • 2023-03-21
    • 2015-01-15
    • 2021-03-11
    相关资源
    最近更新 更多