【问题标题】:Is it ok to return a rvalue reference?可以返回右值引用吗?
【发布时间】:2020-05-14 07:17:43
【问题描述】:
#include <stdio.h>
#include <memory>

struct A {
  A() { printf("A()\n"); }
  virtual ~A() { printf("~A()\n"); }
};

struct B : public A {
  B() { printf("B()\n"); }
  virtual ~B() { printf("~B()\n"); }
};

A&& foo() {
  B b;
  return std::move(b);
}

int main() {
  A&& a = foo();
//  const A& a = foo(); // same output
  printf("checkpoint\n");
}

output:
A() 
B() 
~B() 
~A() 
checkpoint

检查点是最后一行,为什么?我想让 foo 返回引用而不是指针来实现多态,有没有办法做到这一点?

PS:这个问题可能相关:Why a & b have the same address?

更重要的是,我只是一个返回函数内创建的对象的方案,但不使用指针。出于一个原因,指针需要删除,出于另一个原因,认为 A & B 是支持基于范围的 for 循环的迭代器,其取消引用 operator* 被重新定义,使用指针或 unique_ptr/shared_ptr 不是一个好主意,我猜。

【问题讨论】:

  • 可以返回左值/右值引用,但不能返回悬空引用。
  • 函数局部变量在函数返回时被销毁,因此它的右值引用变得无效。指针和左值引用也是如此,因此从这个意义上说,右值引用并没有什么特别之处。
  • 那么如何返回一个在移动语义中创建的非悬空的?没有指针,没有重新定义 operator* 的 unique_ptr 或 shared_ptr,因为我正在制作 A & B 某种迭代器,它们也定义了自己的 operator*。
  • 随便B foo() {B b; return b;}?
  • foo 必须返回 A,调用者不知道 B。问题是“如何返回基类型引用而不是基类型指针,但开销为零(无复制)”跨度>

标签: c++


【解决方案1】:

这是格式错误的,因为foo() 正在返回对本地对象的引用,当foo() 返回时该对象将被销毁;那么返回的引用总是悬空的。

您可以改用智能指针(如std::unique_ptr)。例如

std::unique_ptr<A> foo() {
  std::unique_ptr<A> b = std::make_unique<B>();
  return b;
}

LIVE

【讨论】:

    【解决方案2】:

    看到这个答案:Is returning by rvalue reference more efficient?

    A&& foo() { 与 B b;返回 std::move(b); } 您正在返回一个悬空引用:https://godbolt.org/z/HU88aU

    我不太明白返回引用(而不是指针)与多态性有什么关系? B 基于 A 并且在 foo() 中创建实例会超过对象的生命周期 - 仅此而已。

    【讨论】:

    • 因为返回指针需要删除。甚至 unique_ptr 或 shared_ptr 也无济于事,因为它们重新定义了 operator*。认为 A 是某种抽象迭代器,B 是具体迭代器。如果可以使用 refs 代替指针,它会有所帮助。
    【解决方案3】:

    这在很多层面都是错误的。

    多态与使用引用和指针没有任何关系。例如,当从类层次结构中调用对象的成员函数时,就会发生多态性。根据对象的确切类型,它将导致执行不同的函数。如果您有一个指向该对象的指针并调用foo-&gt;bar() 或对它的引用并调用foo.bar(),这并不重要。无论哪种方式,你都会得到多态性。

    通过手动声明析构函数,您可以防止自动生成移动构造函数和移动赋值运算符。当试图移动没有移动语义的东西时,它会被复制。因此,即使您只是添加一个析构函数来记录日志,您也需要手动启用移动:

    A(A&&) = default;
    

    您似乎不了解右值引用的工作原理。我不会在这里详细解释整个主题,但std::move 本质上只是将左值转换为右值。您无需声明左值并将其转换为右值即可获得右值。所以这个:

    B b;
    return std::move(b);
    

    可以改为:

    return B {};
    

    这会将您的代码更改为:

    #include <iostream>
    #include <memory>
    
    struct A {
    
        A()
        { 
            std::cout << "A()" << std::endl;
        }
    
        A(A&&) = default;   
    
        virtual ~A()
        { 
            std::cout << "~A()" << std::endl;
        }
    };
    
    struct B : public A 
    {
        B()
        {
            std::cout << "B()" << std::endl;
        }
    
        B(B&&) = default;
    
        virtual ~B() 
        { 
            std::cout << "~B()" << std::endl;
        }
    };
    
    A foo() 
    {
        return B {};
    }
    
    int main() 
    {
        A a = foo();
    
        std::cout << "checkpoint" << std::endl;
    }
    

    【讨论】:

    • 我了解参考的工作原理。因为我才读了两天,所以我不太了解 rvalue ref。我只是想用它来解决我的问题。您的代码没有帮助,有一个复制操作。我想通过 ref 创建和返回,没有任何副本。
    • @willzeng 不,你不明白。我的代码中没有复制。事实上,A 和 B 甚至删除了它们的复制构造函数和赋值运算符,因为我声明了一个移动构造函数。一个富;一间酒吧;富=酒吧;甚至不会编译。
    • 只需编译并运行并查看输出。在“检查点”之前我不想要任何析构函数
    • @willzeng 这不是 C++ 中移动的工作原理。被移出的对象不仅会消失,它还必须处于有效状态,并且它的析构函数才会运行。
    • 你的意思是一开始就达不到目标?您可能会在我的问题中看到另一篇帖子“为什么 a 和 b 具有相同的地址”,a 和 b 甚至具有相同的地址。这激发了我寻求这种方法的灵感。
    猜你喜欢
    • 2013-04-20
    • 1970-01-01
    • 2015-07-17
    • 2018-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-13
    • 1970-01-01
    相关资源
    最近更新 更多