是否应该从现在开始声明所有C ++函数的rvalue?

时间:2013-12-26 19:06:38

标签: c++ c++11

为什么我应该或不应该创建我的所有函数和成员函数来获取rvalue并省略带lvalue的版本?您可以随时将lvalue转发给右值,对吗?我甚至可以const rvalue,所以为什么这是一个坏主意或好主意?

我在代码中的含义如下。 "&&" rvalue引用允许用户使用临时值,并且仍然可以通过简单的转发使用lvalue。因此,考虑到这一点,为什么我应该在c ++ 11中提供print_string(string& str)lvalue引用)任何函数(除了const引用,因为rvalue有好处) ?

#include <iostream>
#include <string>
#include <utility>

using namespace std;


void print_string(string&& str)
{
    str += "!!!";
    cout << str << endl;
}
void print_string2(string& str)
{
    str += "???";
    cout << str << endl;
}

int main(int argc, char* argv[])
{

    print_string("hi there");  // works
    print_string(string("hi again")); // works
    string lvalue("lvalue");
    print_string(forward<string>(lvalue)); // works as expected
    //print_string(lvalue);  // compile error

    //print_string2("hi there"); // comile error
    //print_string2(string("hi again")); // compile error
    print_string2(lvalue);


    char a;
    cin >> a;
}

输出

你好!!!
你好,我们又见面了!!!
左值!
左值!!! ???

void print_string(string&& str)提供了比void print_string2(string& str)更灵活的用例,那么为什么我们不应该一直使用rvalue参数呢?

3 个答案:

答案 0 :(得分:6)

与C ++的许多功能一样,有一个如何使用左值和右值引用的约定。

约定的另一个例子是运算符,尤其是==!=。您可以重载它们以返回double,并复制文件以进行比较。但按照惯例,它们返回bool并且只比较左右操作数而不进行修改。


rvalue引用的约定是它们引用资源可能被“窃取”的对象:

  • 左值引用意味着该对象归其他人所有
  • 右值引用意味着我们可以从对象中窃取资源

引入了Rvalue引用来支持移动语义,即资源所有权的转移。移动语义通常意味着可以移动绑定到右值引用的对象。从传统上移动的对象被假定为有效但未知状态。例如:

// returns a string that equals i appended to p
string join(string&& p, int i); // string&& means: I do something to p. Or not.

// append i to p
void append(string& p, int i);


string my_string = "hello, world."; // length is 13

append(my_string, 42); // I know what state my_string is specified to be in now
my_string[12] = "!"; // guaranteed to work

string result = join( std::move(my_string), 42 );
// what state is my_string now in?
char c = my_string[1]; // is this still guaranteed to work?


string const cresult = join("hello, number ", 5);
// fine, I don't care what state the temporary is in -- it's already destroyed

人们可以想到如下定义:

string join(string&& p, int i)
{  return std::move(p) + std::to_string(i);  }

void append(string& p, int i)
{  p += std::to_string(i);  }

这个join真正的定义p窃取了资源,并且标准确实没有在操作后指定p的状态{ {1}}(创建一个从std::move(p) + std::to_int(i)窃取资源的临时文件。)

对于这个简单的例子,您仍然可以使用

p

“替换”string result = "hello "; result = join(std::move(result), 42); 。但是移动也可能很昂贵(它们总是复制某些东西),并且这种技术不能轻易替换多个左值参考参数。

<子> N.B。在我看来,你应该在函数的名称中指出它是否需要左值引用并修改参数。我当然不希望名为append的函数修改我传入的字符串。


恕我直言,按照惯例,移动的对象处于有效但未指定的状态,应该阻止你在任何地方使用右值引用。

此外,与使用print_string2的就地操作相比,使用过多移动可能会对性能产生轻微影响。

答案 1 :(得分:5)

各种stackexchange问​​题帮助我回答了我自己的问题。

我前进并感到困惑。这个答案有帮助(Can I typically/always use std::forward instead of std::move?)你可以r / forward / move / g,我的问题仍然是一样的。请务必直接阅读Scott Meyer对此问题的回答:http://scottmeyers.blogspot.co.uk/2012/11/on-superfluousness-of-stdmove.html

这是一个好主意吗?好吧,这些帖子帮我解决了这个问题:Is pass-by-value a reasonable default in C++11?

Should all/most setter functions in C++11 be written as function templates accepting universal references?

Should I always move on `sink` constructor or setter arguments?

答案 2 :(得分:0)

print_string2是一个糟糕的设计。它修改了字符串。例如,有人可能会写:

string s{"hello"};
print_string2(s);     // print the string

foo(s);      // work on the string - oops, it has junk punctuation on it now.

如果函数修改了调用者的字符串,则不应该将其称为print_

所以{J} string&&优于string&,但这两个选项都不是最佳选择。

相反,此函数可以按值接受字符串:

void print_string2(string s)

然后,如果用rvalue(包括xvalues)调用它,则将参数移入s,如果用左值调用它,则会生成副本,这是理想的行为。

如果你的函数不需要修改字符串作为其处理的一部分,那么你应该传递const引用,它可以绑定到rvalues和lvalues:

void print_string(const string &s) 
{

在您的示例中,这是最佳选项,因为在打印左值时无需复制字符串。身体将是:

cout << str << "???" << endl;