更新时间:2021-09-12 05:14:57
简单的答案是在大多数情况下,将临时变量传递给期望可变左值引用的函数表示逻辑错误,而c ++语言正在尽最大努力帮助您避免发生错误。
The simple answer is that in most cases, passing a temporary to a function that expects a mutable lvalue reference indicates a logic error, and the c++ language is doing its best to help you avoid making the error.
函数声明: void foo(Bar& b)
提出以下叙述:
The function declaration: void foo(Bar& b)
suggests the following narrative:
foo引用了将要修改的Bar
b
。因此,b
既是输入又是输出
foo takes a reference to a Bar,
b
, which it will modify.b
is therefore both an input and an output
通常,将临时值作为输出占位符 的逻辑错误比调用返回对象的函数要糟糕得多,该函数只会丢弃未经检查的对象。
Passing a temporary as the output placeholder is normally a much worse logic error than calling a function which returns an object, only to discard the object unexamined.
例如:
Bar foo();
void test()
{
/*auto x =*/ foo(); // probable logic error - discarding return value unexamined
}
但是,在这两个版本中,没有问题:
However, in these two versions, there is no problem:
void foo(Bar& b)
foo拥有Bar引用的对象的所有权
foo takes ownership of the object referenced by Bar
void foo(Bar b)
foo从概念上讲是复制Bar,尽管在很多情况下在某些情况下,编译器会认为不必要创建和复制Bar。
foo conceptually takes a copy of a Bar, although in many cases the compiler will decide that creating and copying a Bar is un-necessary.
所以问题是,我们要实现什么? ?如果我们只需要在其上工作的酒吧,则可以使用 Bar&& b
或 Bar b
版本。
So the question is, what are we trying to achieve? If we just need a Bar on which to work we can use the Bar&& b
or Bar b
versions.
如果我们想也许使用临时栏,而也许使用现有的酒吧栏,那么我们很可能将需要两个 foo
重载,因为它们在语义上会有所不同:
If we want to maybe use a temporary and maybe use an existing Bar, then it is likely that we would need two overloads of foo
, because they would be semantically subtly different:
void foo(Bar& b); // I will modify the object referenced by b
void foo(Bar&& b); // I will *steal* the object referenced by b
void foo(Bar b); // I will copy your Bar and use mine, thanks
如果我们需要这种选择,我们可以创建
If we need this optionality, we can create it by wrapping one in the other:
void foo(Bar& b)
{
auto x = consult_some_value_in(b);
auto y = from_some_other_source();
modify_in_some_way(b, x * y);
}
void foo(Bar&& b)
{
// at this point, the caller has lost interest in b, because he passed
// an rvalue-reference. And you can't do that by accident.
// rvalues always decay into lvalues when named
// so here we're calling foo(Bar&)
foo(b);
// b is about to be 'discarded' or destroyed, depending on what happened at the call site
// so we should at lease use it first
std::cout << "the result is: " << v.to_string() << std::endl;
}
有了这些定义,这些现在都合法了:
With these definitions, these are now all legal:
void test()
{
Bar b;
foo(b); // call foo(Bar&)
foo(Bar()); // call foo(Bar&&)
foo(std::move(b)); // call foo(Bar&&)
// at which point we know that since we moved b, we should only assign to it
// or leave it alone.
}
好的,为什么要这么照顾呢?
OK, by why all this care? Why would it be a logic error to modify a temporary without meaning to?
好吧,想象一下:
Bar& foo(Bar& b)
{
modify(b);
return b;
}
我们希望这样做:
extern void baz(Bar& b);
Bar b;
baz(foo(b));
现在想象这可以编译:
auto& br = foo(Bar());
baz(br); // BOOM! br is now a dangling reference. The Bar no longer exists
因为我们***在 foo
, foo
的作者可以确信,该错误永远不会在您的代码中发生。
Because we are forced to handle the temporary properly in a special overload of foo
, the author of foo
can be confident that this mistake will never happen in your code.