C++ 引用限定符:深度解析与实践

作者注

本课为选读内容。建议通读以了解基本概念,无需深入掌握即可继续后续课程。

问题回顾:右值对象返回引用的风险

在《返回数据成员引用的成员函数》中,我们指出:当通过返回引用的访问函数调用右值对象时,极易产生悬空引用。简例如下:

#include <iostream>
#include <string>
#include <string_view>

class Employee
{
private:
    std::string m_name{};

public:
    Employee(std::string_view name) : m_name{ name } {}
    const std::string& getName() const { return m_name; } // 返回 const 引用
};

Employee createEmployee(std::string_view name)
{
    Employee e{ name };
    return e; // 返回值是右值
}

int main()
{
    // 情况 1:安全——同一表达式内用完即弃
    std::cout << createEmployee("Frank").getName() << '\n';

    // 情况 2:危险——保存引用供后续使用
    const std::string& ref{ createEmployee("Garbo").getName() }; // 右值对象销毁后 ref 悬空
    std::cout << ref << '\n'; // 未定义行为
}

在“情况 2”中,createEmployee("Garbo") 产生的右值对象在初始化 ref 后即被销毁,ref 成为悬空引用,后续使用导致未定义行为。


两难抉择

  • getName() 按值返回,右值调用安全,但在常见左值调用时会带来不必要的拷贝开销。
  • getName() 返回 const 引用,左值调用高效,但右值调用易被误用。

传统做法是返回 const 引用,并告诫使用者“不要在右值上保存引用”。
C++11 提供了更优雅的方案:引用限定符(ref-qualifiers)


引用限定符

通过给成员函数添加 &&& 限定符,可以根据隐式对象是左值还是右值提供重载:

const std::string& getName() const &  { return m_name; } // 仅匹配左值
std::string        getName() const && { return m_name; } // 仅匹配右值
  • & 限定:仅当隐式对象为左值时调用;返回 const 引用,零拷贝。
  • && 限定:仅当隐式对象为右值时调用;返回,安全但需一次拷贝/移动。

完整示例:

#include <iostream>
#include <string>
#include <string_view>

class Employee
{
private:
    std::string m_name{};

public:
    Employee(std::string_view name) : m_name{ name } {}

    const std::string& getName() const &  { return m_name; } // 左值版本
    std::string        getName() const && { return m_name; } // 右值版本
};

Employee createEmployee(std::string_view name)
{
    return Employee{ name };
}

int main()
{
    Employee joe{ "Joe" };
    std::cout << joe.getName() << '\n';           // 左值调用 & 版本

    std::cout << createEmployee("Frank").getName() << '\n'; // 右值调用 && 版本
}

进阶:右值版本使用 std::move

若右值对象为非 const,其成员即将随对象销毁,可通过 std::move 避免拷贝:

std::string getName() && { return std::move(m_name); }

可与 const && 版本并存,或完全替代之(因 const 右值较少见)。
std::move 用法见第 22.4 课。


注意事项

  1. 不能共存:同一函数若存在引用限定符重载,则不能再存在无引用限定符的版本。
  2. 默认回退:若仅提供 const & 版本,右值对象亦可调用该版本(类似 const T& 可绑定右值)。
  3. 显式删除= delete 可禁止某限定符版本,例如删除 && 版本即可阻止右值调用。

为何不推荐广泛使用引用限定符?

尽管优雅,但存在以下缺点:

  • 代码膨胀:为每个返回引用的访问函数增加右值重载,增加维护负担,而右值误用场景并不常见且易于避免。
  • 强制拷贝:右值重载按值返回,可能产生不必要的拷贝/移动开销。
  • 认知度低:大多数开发者不熟悉此特性;标准库亦极少使用。

因此,我们不推荐将引用限定符作为最佳实践。
更简洁的做法是:立即使用访问函数返回值,不要将其引用保存供后续使用

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"