【发布时间】:2021-10-26 22:14:33
【问题描述】:
我遇到了一些代码,这些代码在 lambda 中传递的函数参数中使用了对 std::function 的 const 右值引用。令人困惑的是,它随后对这个传入的参数进行了std::move 调用。像这样的:
using CallbackFn = std::function<void()>;
using AnotherCbFn = std::function<void(int)>;
void bar(AnotherCbFn&& cb) {
// doSomething();
}
void foo(CallbackFn const&& cb) {
// Some code
bar([ x = std::move(cb) /* <-- What's this? */](int value){
x();
});
}
void baz() {
foo([](){
// doSomethingMore();
});
}
传入 const-value 引用然后在它们上调用 std::move 的目的是什么?所以我尝试了一个更简单的代码 sn-p 看看在这种情况下会发生什么
#include <utility>
#include <string>
#include <cstdio>
#include <type_traits>
struct Foo {
Foo() = default;
Foo(Foo&& o) {
str = std::move(o.str); // calls the move assignment operator
std::printf("Other [%s], This [%s]\n", o.str.data(), str.data());
}
Foo(Foo const&& o) {
str = std::move(o.str); // calls the copy assignment operator
std::printf("Other [%s], This [%s]\n", o.str.data(), str.data());
}
private:
std::string str = "foo";
};
template <typename T>
void f(T&& x) {
if constexpr(std::is_const_v<T>) {
std::printf("Const rvalue\n");
auto temp = std::move(x);
} else {
std::printf("non-const rvalue\n");
auto temp = std::move(x);
}
}
Foo const getConstRvalue() {
return Foo();
}
Foo getNonConstRvalue() {
return Foo();
}
int main() {
f(getConstRvalue());
f(getNonConstRvalue());
}
产生了输出:
Const rvalue
Other [foo], This [foo]
non-const rvalue
Other [], This [foo]
在 godbolt(here) 上检查组件确认发生了什么。 Foo(const&&) 调用 std::string 的 copy-assignment 运算符:
调用 std::__cxx11::basic_string
::operator=(std::__cxx11::basic_string const&)
而Foo(Foo&&) 调用std::string 的移动赋值 运算符:
调用 std::__cxx11::basic_string
::operator=(std::__cxx11::basic_string &&)
我认为(请纠正我!)const-lvalue 函数参数也可以绑定到 const rvalue 参数(以及非 const rvalue, const 左值和非 const 左值),这就是为什么在 Foo(const&&) 的情况下会有一个副本,因为 std::string 的 const 右值不能绑定到移动赋值运算符中的非 const 右值。
那么,传递 const rvalue 引用然后在其上调用 std::move 的目的是什么,因为调用 std::move 通常意味着在此之后不应该使用该值,在这种情况下,实际上涉及到一个副本所需的移动语义?有什么微妙的语言机制在起作用吗?
【问题讨论】:
-
您是否只在一个代码库或多个地方看到了这个
const&&?我从来没有在任何一本书中看到过,指导方针......Apparently它只是为了被禁用...... -
对我来说看起来像是一个有问题的编译器的一些解决方法。当所有这些东西都是全新的时,可能会出现编译器错误,您必须执行
T const&&重载才能构建代码。我不记得细节了。到现在已经很多年了。 -
Foo(Foo&& o)是一个移动构造函数,因此Foo(Foo const&& o)显然是一个复制构造函数,因此它应该被声明为Foo(Foo const& o)(并从中删除move()),使用 const 左值引用而不是 const 右值引用。 -
@TedLyngmo 是的……就是这样。它是由一位高级开发人员编写并由一位高级开发人员审查的......因此我的问题是衡量我是否错过了一些基本的东西。
-
我明白了... :-) Re:
bar(std::move(cb)); // <<-- What's this?- 它使用const CallbackFn&调用函数bar。