【问题标题】:What is the point of returning a const alias from a function从函数返回 const 别名有什么意义
【发布时间】:2021-04-17 08:17:28
【问题描述】:

请看下面的代码,帮助我理解: 为什么存在将 const 别名返回到像我的 f2 函数这样的文字的功能。我不明白重点是什么。 f2 和 f3 之间的区别在于 const 确实允许我在 return 语句中放置一个文字,但是为什么?

感谢任何帮助理解这一点。

#include <iostream>

const int f1(int a)
{
    return 15;
}

const int& f2(int a)
{
    return 14;
}

int& f3(int a)
{
    a = 12;
    return a;
}

int main()
{

    auto a{ 10 };
    auto b = f1(a);
    auto c = f2(a);
    auto d = f3(a);

    std::cout << a << " " << b << " " << c << " " << d << std::endl;
    a = 1;
    b = 2;
    c = 3;
    d = 4;
    std::cout << a << " " << b << " " << c << " " << d << std::endl;
}

【问题讨论】:

  • 使用从f2f3 返回的引用会导致未定义的行为。没什么好说的了。从f1 返回const int 没有意义。无论如何,一般来说,不同之处在于 const-lvalue-references 可能会绑定到临时变量(右值),而 non-const-lvalue-references 可能不会。
  • 别名是类型的次要名称。你说的是引用。
  • 一个好的编译器会警告你这段代码。此代码错误,可能会崩溃(请参阅末尾的“Segmentation fault”):wandbox.org/permlink/OAO8QVbZ3FgAJ4GX
  • 谢谢大家的澄清。我知道代码存在重大问题,但我不清楚为什么允许它,以及实际发生了什么。再次感谢您。

标签: c++ function return constants


【解决方案1】:

f2f3 都有未定义的行为。您正在返回对局部变量的引用。这些局部变量在函数结束时被销毁,引用悬空。

const 引用和 non-const 引用的区别在于 const 引用可以绑定到右值和左值。

对于非常量引用,您必须区分左值引用(int&amp;) 和右值引用(int&amp;&amp;)。

因此使用函数签名int&amp;&amp; f2(int a) 也可以编译,但同样具有未定义的行为。

这很有用的主要原因是因为当我们传递对函数的引用时,函数签名会告诉我们我们期望的是左值还是右值。我们也可以重载两者,并根据我们得到的内容决定移动/复制。

在我们不关心的情况下,或者如果我们只想从值中读取,我们可以使用 const 引用并且能够接受传入的左值和右值。

void foo(MyClass& mc) {
    // We know mc is an lvalue.
    // We could copy mc, or modify it if we want to use it as an output parameter.
}

void foo(MyClass&& mc) {
    // We know mc is an rvalue.
    // We know it would be safe to move from mc in this case.
}

MyClass mc;
foo(mc); // Callsthe first overload
foo(MyClass{}); // Calls the second overload

// The two functions above can be overloaded, so we can make sure we deal
// with both cases in the right way

void foo2(const MyClass& mc) {
    // This can be both an rvalue or lvalue.
    // We don't really care since the reference
    // is const we are only going to read from it.
}

foo2(mc); // Both calls work
foo2(MyClass{});

【讨论】:

    【解决方案2】:

    main 中的 bcd 变量使用函数返回的副本进行初始化。无论他们返回一个副本、一个引用还是一个常量引用。

    为了保留返回值的属性,让我们更改main中的第一行:

    
    int main()
    {
        auto a{ 10 };
        auto& b = f1(a); // Does not compile, a ref can't be tied to a r-value
        auto& c = f2(a); // Ok, c's type is 'const int&'
        auto& d = f3(a); // Ok, d's type is 'int&'
    
        std::cout << a << " " << b << " " << c << " " << d << std::endl;
        a = 1;
        b = 2;
        c = 3; // Does not compile. c is a ref to a const
        d = 4;
        std::cout << a << " " << b << " " << c << " " << d << std::endl;
    }
    

    所以,重点是您可以返回对内部变量的引用,但现在允许调用者更改它。这样做(而不是返回副本),您

    • 避免复制
    • 允许调用者查看以后的任何更改

    上面的代码意义不大,但是想想类内部的一个方法,其中内部变量可以用其他方式改变。

    除了返回类型之外,f2 和 f3 也不正确,因为它们返回对非内存 (f2) 或临时对象 (f3) 的引用。

    【讨论】:

      【解决方案3】:

      假设您编写了一个需要返回一个复杂对象的函数,但该对象不应被修改(例如指向共享资源的指针、类属性、某种单例数据等)。 为了这个答案,让我们假设“struct Point”中的类型。 您有两种选择:

      1. 按值返回它,这将创建其原始类型成员的深层副本和其按引用类型成员的浅层副本: const struct Point f2(...)
      2. 通过引用返回它,这将只复制指向对象的指针: 常量结构点* f2() const struct Point& f2()

      两者都有效,而第二个在处理重物时具有优势。

      在您提供的代码中,您看不到差异,因为“int”是一种原始类型,这意味着它具有已知的复制方式。这意味着 var "c" 实际上不是别名也不是 const,它是一个从 f2 的返回类型中获取其值的 int

      【讨论】:

        猜你喜欢
        • 2021-08-06
        • 2011-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-01
        • 2011-12-04
        • 2020-09-29
        相关资源
        最近更新 更多