为什么我不能将一个常量左值引用绑定到返回 T&& 的函数?

8

我曾将一个函数的返回值绑定到一个常量左值引用上,但是该对象在常量左值引用的生命周期结束之前被删除了。

在以下示例中,Foo 对象在 foo 生命周期结束之前被销毁:

#include <iostream>
#include <string>

struct Foo
{
    ~Foo()
    {
        std::cout << "Foo destroyed: " << name << std::endl;
    }
    std::string name;
};

Foo&& pass_through(Foo&& foo)
{
    return std::move(foo);
}

int main()
{
    const Foo& foo = pass_through({"some string"});
    std::cout << "before scope end" << std::endl;
}

输出结果为:

Foo destroyed: some string
在作用域结束之前

在coliru上运行:1

我以为你可以将const T&绑定到任何东西。返回T&&是不是一种不好的做法,应该优先返回值?

我在cpprestsdk中偶然发现了这个问题:

inline utility::string_t&& to_string_t(std::string &&s) { return std::move(s); }

https://github.com/Microsoft/cpprestsdk/blob/master/Release/include/cpprest/asyncrt_utils.h#L109

这很令人困惑,因为预处理器宏分派的 to_string_t 的 Windows 版本按值返回

_ASYNCRTIMP utility::string_t __cdecl to_string_t(std::string &&s);

编辑: 为什么将 pass_through 的结果传递给一个接受 const Foo& 的函数时可以正常工作?这种情况下寿命被延长了吗?


早上太早了,还没想清楚,但显式析构函数可以防止移动构造函数被隐式创建。https://en.cppreference.com/w/cpp/language/move_constructor - Kenny Ostrom
9
仅当引用直接绑定到临时对象时,寿命延长才适用。 - T.C.
为什么 foo 引用的生命周期会超过 const Foo& foo = pass_through({"some string"}); 这一行的执行? - user4290866
2
你可以将它作为参数传递,因为临时对象的生命周期持续到创建它的“完整表达式”的结束。(非常不正式地说,“直到下一个分号”。) 不需要扩展。 - molbdnilo
1
静态分析应该能够检测到这个问题,我预测未来编译器将能够警告这个问题。 - M.M
显示剩余3条评论
1个回答

3

从标准来看:

15.2 Temporary objects

6.9 A temporary object bound to a reference parameter in a function call persists 
    until the completion of the full-expression containing the call.
基本上它的含义是,因为你传入了一个临时对象并没有延长其生命周期(比如将其移动到左值中),所以它的生命周期仅限于调用 pass_through 后的第一个 ;。此后,你只剩下了一个悬空引用 foo
int main()
{
    const Foo& foo = pass_through({"some string"}); // "some string" lifetime ends here
    std::cout << "before scope end" << std::endl;
}

关于返回rvalue引用的最佳实践,我认为下面这两个答案已经详细阐述了这个问题: 是否应该通过rvalue引用返回rvalue引用参数? 通过rvalue引用返回更有效吗?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接