【问题标题】:C++11: Do move semantics get involved for pass by value?C ++ 11:移动语义是否涉及按值传递?
【发布时间】:2012-03-15 17:18:57
【问题描述】:

我有一个如下所示的 API:

void WriteDefaultFileOutput(std::wostream &str, std::wstring target)
{
    //Some code that modifies target before printing it and such...
}

我想知道通过这样做来启用移动语义是否明智:

void WriteDefaultFileOutput(std::wostream &str, std::wstring&& target)
{
    //As above
}
void WriteDefaultFileOutput(std::wostream &str, std::wstring const& target)
{
    std::wstring tmp(target);
    WriteDefaultFileOutput(str, std::move(tmp));
}

或者这只是编译器应该能够弄清楚的样板?

【问题讨论】:

  • wstring 有一个移动构造函数,所以这不是已经发生的事情了吗?
  • 你不能在函数调用中直接避开tmp和std::move并传递wstring(target)吗?
  • 这可能是一个愚蠢的问题,但为什么传递一个普通的 const 引用不是一个选项?将某些内容(此处为字符串)写入流不应修改它,也不需要额外的私有副本。因此,我真的看不出首先制作(可能很深)临时副本然后在临时副本上使用(破坏性)移动语义的原因。当然,除了使用移动语义。
  • @Damon : 代码中的注释明确显示//Some code that modifies target before printing it and such...
  • 啊,我真傻……错过了那个评论,谢谢。

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


【解决方案1】:

“按值传递”可以表示复制或移动;如果参数是左值,则调用复制构造函数。如果参数是右值,则调用移动构造函数。您无需执行任何涉及右值引用的特殊操作,即可通过值传递获得移动语义。

我在What are move semantics? 中深入探讨了这个主题

【讨论】:

  • 在按值传递和右值参数的情况下,我希望临时在需要的地方准确构建,这样就不会移动任何东西。或者复制,在旧的编译器中。
  • 也许;编译器优化领域不是我的领地。
  • 这几乎不是我所说的优化(尽管官方是这样)。删除复制构造函数会导致程序语义发生变化,这只是合法的,因为标准给予编译器明确授权可以这样做,并且因为它可能会改变语义,所以在评估程序正确性时必须考虑到这一点。
  • 无论如何,你提到的优化只能用prvalues,而不是xvalues。
【解决方案2】:

如果您要使用不可移动的值制作副本,您应该更喜欢按值传递。例如,WriteDefaultFileOutputconst& 版本显式复制参数,然后使用移动版本移动副本。这意味着如果 WriteDefaultFileOutput 使用可移动值(xvalue 或 prvalue)调用,那么它将被移动,如果使用左值调用,它将被复制。

这意味着这个函数的两种形式之间没有的区别。考虑一下:

WriteDefaultFileOutput(L"SomeString");

在您的第一种情况下,它将创建一个临时的wstring。临时变量是纯右值,所以它会被“移动”到参数中(因为wstring 有一个移动构造函数)。当然,任何值得一提的编译器都会忽略这一举动,而只是将临时变量直接构造到参数中。

在您的第二种情况下,或多或少会发生相同的事情。创建一个临时的wstring。临时变量是纯右值,因此它们可以绑定到&& 参数类型。因此,它将使用对临时的 r 值引用来调用函数的第一个版本。唯一可能的区别是编译器不会忽略这一举动。即使这样,移动也不会那么昂贵(取决于您的basic_string 实施)。

现在考虑一下:

std::wstring myStr{L"SomeString"};
WriteDefaultFileOutput(myStr);

在第一种情况下,对WriteDefaultFileOutput 的调用将导致将myStr 值复制到函数参数中。

在第二种情况下,myStr 是一个左值。它不能绑定到&& 参数。因此,它可以调用的唯一版本是const& 版本。该函数将手动构建一个副本,然后将副本与另一个副本一起移动。

同样的效果。您的第一个版本的代码较少,因此出于显而易见的原因,请继续使用。

总的来说,我会说只有两个原因将参数作为&&

  1. 您正在编写移动构造函数。
  2. 您正在编写转发函数,需要使用完美转发。

在您希望移动成为可能的所有其他情况下,只需取一个值。如果用户想复制,让他们复制。我想如果您想明确禁止复制参数,您可以使用&&。但主要问题是清晰度。

如果你取一个值参数,并且用户提供了一个可移动的值,那么用户提供的值将总是被移动。例如,您的&& 版本的WriteDefaultFileOutput 没有 来实际从其参数中移动数据。它当然可以。但这不是必须的。如果它取了一个值,那么它就已经声明了数据。

因此,如果一个函数接受一个值参数,并且您在该值中看到 std::move,那么您知道被移动的对象现在是空的。 保证已被移出。

【讨论】:

    猜你喜欢
    • 2013-05-19
    • 1970-01-01
    • 1970-01-01
    • 2013-09-04
    • 2012-01-15
    • 2014-08-16
    • 2019-01-02
    • 2011-06-26
    相关资源
    最近更新 更多