为什么我应该或不应该创建我的所有函数和成员函数来获取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参数呢?
答案 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 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;