【问题标题】:C++11: std::move() call on arguments' listC++11:std::move() 调用参数列表
【发布时间】:2014-09-30 13:35:37
【问题描述】:

在参数列表中的对象上操作是否安全,当该对象上还调用了 std::move() 时?

void foo(int* raw, std::unique_ptr<int> u)
{
    *raw = 456;
}

std::unique_ptr<int> p(new int(123));
foo(p.get(), std::move(p));

如果 std::move(p) 被评估为第一个参数,那么 foo() 中的“原始”指针是否有效?

【问题讨论】:

  • 考虑到参数的评估顺序没有指定并且可以以任何顺序发生,那么不,这不安全。如果首先评估 std::move 操作,则 p.get() 将返回 nullptr
  • std::move 什么都不做,所以这可能无关紧要。这是 std::unique_ptr&lt;int&gt; 的构造函数,隐式调用,具有破坏性。
  • @MarcAndreson 没错。 (我确实试图在我的评论中提到这一点。)我想指出这一点,因为如果你的函数有一个 std::unique_ptr&lt;int&gt; &amp;&amp; 参数,那么绝对没有问题,即使调用看起来完全一样,包括 @987654330 @ 部分。

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


【解决方案1】:

不,它安全。参数的 eval 顺序是 not 在标准中指定的。所以你的代码可以运行为:

  1. std::move(p)
  2. 调用std::unique_ptr&lt;int&gt;的移动构造函数。
  3. p.get()(因为 2.,这将是 nullptr.)并传递此参数。
  4. 致电foo

你必须这样做:

int *raw = p.get();
foo(raw, std::move(p));

请注意,您的代码可以正常运行,因为某些编译器可以将您的代码编译为 3 -> 1 -> 2 -> 4。但是,这并不意味着代码是安全的。它没有指定标准>o

【讨论】:

  • 但是std::move 什么也没做。这只是一个类型转换。
  • @iavr,是的,但 std::unique_ptr&lt;int&gt; u 是在此组合中创建的,它是未指定顺序的一部分(因此它可以随时发生(在 1.3. 之间(更新4.)) 在调用 foo 之前)。
  • 所以如果 foo() 只是将 std::forward() 参数传递给其他函数而不是对它们进行操作,那么这将是安全的,因为在参数被转发之前,它们会被评估,对吧?
  • @MarcAndreson 是的,如果foo的参数类型为右值引用,即`std::unique_ptr &&。但我不建议这样做 - 代码越简单越好。
  • @MarcAndreson 无论是否内联,在调用之前都会评估参数。 (看来我还没明白你的意思,因为我的理解力或英语水平不佳>o
【解决方案2】:

Here are answers about argument evaluation order - 简而言之:标准中未指定顺序,可能因平台、编译器和调用约定而异。

但我想测试它,所以这里是 Cygwin GCC 的结果:

#include <iostream>
#include <memory>
using namespace std;
void print(int* p) {
    cout << (p == nullptr ? "null" : "valid") << endl; }
void foo(int* p, unique_ptr<int> u) {
    print(p); }
void bar(unique_ptr<int> u, int* p) {
    print(p); }
__stdcall void foo2(int* p, unique_ptr<int> u) {
    print(p); }
__stdcall void bar2(unique_ptr<int> u, int* p) {
    print(p); }
__cdecl void foo3(int* p, unique_ptr<int> u) {
    print(p); }
__cdecl void bar3(unique_ptr<int> u, int* p) {
    print(p); }
int main() {
    unique_ptr<int> p(new int(1)), q(new int(2));
    foo(p.get(), move(p)); bar(move(q), q.get());
    unique_ptr<int> p2(new int(1)), q2(new int(2));
    foo2(p2.get(), move(p2)); bar2(move(q2), q2.get());
    unique_ptr<int> p3(new int(1)), q3(new int(2));
    foo3(p3.get(), move(p3)); bar3(move(q3), q3.get());
}

输出:

空值 有效的 空值 有效的 空值 有效的

令人惊讶的是,即使我使用了__stdcall__cdecl,我也无法强制它更改顺序。

编辑:与 MSVC 2012 相同的测试(__stdcall/__cdecl 移到名称前),相同的结果

【讨论】:

    【解决方案3】:

    在参数上调用std::move() 是非常安全的。

    不安全的做法是通过写入原始指针来绊倒自己,而该指针由已将指针的内存放弃给空闲存储的对象管理。

    【讨论】:

    • 这只是一个说明问题的例子
    • @Marc 我的意思是,使用std::move() 本身并没有什么不安全的地方。您的示例正在做的是未定义的行为。不是因为您使用了std::move(),而是因为您的示例依赖于执行顺序未定义的两个函数的执行顺序。
    猜你喜欢
    • 2014-04-15
    • 2023-03-22
    • 2015-03-21
    • 1970-01-01
    • 2015-09-12
    • 2014-01-17
    • 2016-03-21
    • 2012-03-03
    • 2012-11-17
    相关资源
    最近更新 更多