为什么右值不能绑定到非 const 左值引用,除了写入临时值无效这一事实之外?
Why can an rvalue not bind to a non-const lvalue reference, other than the fact that writing to a temporary has no effect?
我已阅读 the SO question here 并理解这部分答案:"But if you bind a temporary to a non-const reference, you can keep passing it around "forever" 只是为了让您对对象的操作消失,因为在此过程中的某个地方您完全忘记了这是暂时的."
也就是在下面:
#include <iostream>
void modifyValue(int& rValue) {
rValue++;
}
int main() {
modifyValue(9899);
return 0;
}
如果右值可以绑定到非常量左值引用,那么可能会进行许多最终会被丢弃的修改(因为右值是临时的),这是无用的。
这似乎定义得很好(写入临时值就像写入任何值一样,生命周期与写入的有效性无关)。
这是禁止指定绑定的完全正确的理由(即使绑定会被明确定义),但是一旦我认为禁止这种绑定会强制需要转发引用,我的问题就开始形成了。
关于为什么右值不能绑定到非常量左值引用,是否还有其他原因(即除了写入临时值之外)?
简单的答案是在大多数情况下,将临时变量传递给需要可变左值引用的函数表示逻辑错误 , 而 c++ 语言正在尽最大努力帮助您避免犯错误。
函数声明:void foo(Bar& b)
建议以下叙述:
foo takes a reference to a Bar, b
, which it will modify. b
is therefore both an input and an output
传递一个临时变量作为输出占位符通常比调用一个returns一个对象的函数更糟糕的逻辑错误,只是丢弃未经检查的对象。
例如:
Bar foo();
void test()
{
/*auto x =*/ foo(); // probable logic error - discarding return value unexamined
}
不过这两个版本都没有问题:
void foo(Bar&& b)
foo takes ownership of the object referenced by Bar
void foo(Bar b)
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 来工作,我们可以使用 Bar&& b
或 Bar b
版本。
如果我们想可能使用一个临时的并且可能使用一个现有的Bar,那么我们可能需要两个重载foo
,因为它们在语义上会略有不同:
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
如果我们需要这种可选性,我们可以通过将一个包裹在另一个中来创建它:
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;
}
根据这些定义,这些现在都是合法的:
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.
}
好的,为什么这么关心?为什么无意义的修改临时文件会是逻辑错误?
好吧,想象一下:
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
的作者可以确信这个错误永远不会在您的代码中发生。
我已阅读 the SO question here 并理解这部分答案:"But if you bind a temporary to a non-const reference, you can keep passing it around "forever" 只是为了让您对对象的操作消失,因为在此过程中的某个地方您完全忘记了这是暂时的."
也就是在下面:
#include <iostream>
void modifyValue(int& rValue) {
rValue++;
}
int main() {
modifyValue(9899);
return 0;
}
如果右值可以绑定到非常量左值引用,那么可能会进行许多最终会被丢弃的修改(因为右值是临时的),这是无用的。
这似乎定义得很好(写入临时值就像写入任何值一样,生命周期与写入的有效性无关)。
这是禁止指定绑定的完全正确的理由(即使绑定会被明确定义),但是一旦我认为禁止这种绑定会强制需要转发引用,我的问题就开始形成了。
关于为什么右值不能绑定到非常量左值引用,是否还有其他原因(即除了写入临时值之外)?
简单的答案是在大多数情况下,将临时变量传递给需要可变左值引用的函数表示逻辑错误 , 而 c++ 语言正在尽最大努力帮助您避免犯错误。
函数声明:void foo(Bar& b)
建议以下叙述:
foo takes a reference to a Bar,
b
, which it will modify.b
is therefore both an input and an output
传递一个临时变量作为输出占位符通常比调用一个returns一个对象的函数更糟糕的逻辑错误,只是丢弃未经检查的对象。
例如:
Bar foo();
void test()
{
/*auto x =*/ foo(); // probable logic error - discarding return value unexamined
}
不过这两个版本都没有问题:
void foo(Bar&& b)
foo takes ownership of the object referenced by Bar
void foo(Bar b)
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 来工作,我们可以使用 Bar&& b
或 Bar b
版本。
如果我们想可能使用一个临时的并且可能使用一个现有的Bar,那么我们可能需要两个重载foo
,因为它们在语义上会略有不同:
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
如果我们需要这种可选性,我们可以通过将一个包裹在另一个中来创建它:
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;
}
根据这些定义,这些现在都是合法的:
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.
}
好的,为什么这么关心?为什么无意义的修改临时文件会是逻辑错误?
好吧,想象一下:
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
的作者可以确信这个错误永远不会在您的代码中发生。