【问题标题】:Moving from a const rvalue从 const 右值移动
【发布时间】: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&amp;&amp;) 调用 std::stringcopy-assignment 运算符:

调用 std::__cxx11::basic_string::operator=(std::__cxx11::basic_string const&)

Foo(Foo&amp;&amp;) 调用std::string移动赋值 运算符:

调用 std::__cxx11::basic_string::operator=(std::__cxx11::basic_string&&)

认为请纠正我!)const-lvalue 函数参数也可以绑定到 const rvalue 参数(以及非 const rvalue, const 左值和非 const 左值),这就是为什么在 Foo(const&amp;&amp;) 的情况下会有一个副本,因为 std::string 的 const 右值不能绑定到移动赋值运算符中的非 const 右值。

那么,传递 const rvalue 引用然后在其上调用 std::move 的目的是什么,因为调用 std::move 通常意味着在此之后不应该使用该值,在这种情况下,实际上涉及到一个副本所需的移动语义?有什么微妙的语言机制在起作用吗?

【问题讨论】:

  • 您是否只在一个代码库或多个地方看到了这个const&amp;&amp;?我从来没有在任何一本书中看到过,指导方针......Apparently它只是为了被禁用......
  • 对我来说看起来像是一个有问题的编译器的一些解决方法。当所有这些东西都是全新的时,可能会出现编译器错误,您必须执行T const&amp;&amp; 重载才能构建代码。我不记得细节了。到现在已经很多年了。
  • Foo(Foo&amp;&amp; o) 是一个移动构造函数,因此 Foo(Foo const&amp;&amp; o) 显然是一个复制构造函数,因此它应该被声明为 Foo(Foo const&amp; o) (并从中删除 move() ),使用 const 左值引用而不是 const 右值引用。
  • @TedLyngmo 是的……就是这样。它是由一位高级开发人员编写并由一位高级开发人员审查的......因此我的问题是衡量我是否错过了一些基本的东西。
  • 我明白了... :-) Re: bar(std::move(cb)); // &lt;&lt;-- What's this? - 它使用const CallbackFn&amp; 调用函数bar

标签: c++ move


【解决方案1】:

std::move 什么都不移动,它只是将左值(对右值 cb 的引用)重新解释为您忘记显示的某些 bar 函数所期望的右值你的代码 sn-p。

我怀疑它看起来像:

void bar(CallbackFn const&& cb) {
  ...
}

【讨论】:

  • 抱歉遗漏了bar()。我已经更新了问题以标记传递 lambda 的正确流程。
  • @Zoso,我的回答对你有用吗,或者(考虑到你更新了问题但最终没有接受答案)你在这个范围内还有问题要解决吗?
  • 您解释一下std::move 的作用。我的问题与在使用 lambda、字符串等保存副本时可能移动内存内容时在 const 右值引用上调用 std::move 有什么用处有关。我知道std::move 本身不会移动内存,但它生成的右值在被调用方站点用作指示以不同方式使用该内存,以窃取内存。从我从 cmets 收集到的信息来看,这是毫无意义的,而且可能是无意的,正如我在问题中指出的那样,它强制复制语义。
  • @Zoso,好吧,我如何从你的消息中看到上下文(如果我错了,请纠正我):你有 foo 函数,它接受 const&& 和一些获取 const&& 的 bar 函数,这是给定的;所以你无法控制给定的情况。在这种情况下,您能否提供您的版本以在没有 std::move 的情况下从 foo 调用 bar?如果上下文不同(您可以更改签名等),请指定它。从你目前的解释来看,我看不出什么是约束,什么不是。
  • 签名void foo(CallbackFn const&amp;&amp; cb) 不正确,应该只是void foo(CallbackFn&amp;&amp; cb),因为传递一个打算从以后移动的 const 右值引用没有意义。我的问题并不是真正关于 如何 让事情工作/编译,而是更多关于 why 首先有T const&amp;&amp;,它作为在 cmets 和我在问题中分享的示例中进行了讨论,在这种情况下似乎不合适。我希望这能澄清事情。感谢您调查这个问题!
猜你喜欢
  • 2012-06-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-20
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多