C++---ref-qualifier( / )函数的左右值调用的界定
1. ref-qualifier的定义
ref-qualifier 是 C++11 引入的一种成员函数限定符,用来限制成员函数只能在特定引用类型的对象上调用。它有两种形式:
&
:只能在左值对象上调用&&
:只能在右值对象上调用
语法位置在函数的cv-qualifier(const/volatile)之后(如果有),函数体之前。
struct Foo {void bar() &; // 仅左值对象可调用void bar() &&; // 仅右值对象可调用
};
2. ref-qualifier的作用
在 C++11 之前,我们无法区分成员函数是通过左值还是右值对象调用的。例如:
struct String {const char* data() const { return ptr; }
private:const char* ptr;
};String make_string();auto p = make_string().data(); // 危险:返回的指针指向临时对象,临时对象已销毁
使用 ref-qualifier 可以避免这种情况:
struct String {const char* data() const & { return ptr; } // 左值调用const char* data() const && = delete; // 禁止右值调用
private:const char* ptr;
};
这样 make_string().data()
会在编译期被禁止。
3. 基本语法与重载规则
ref-qualifier 参与函数重载决议,规则如下:
- 左值对象优先匹配
&
限定的函数 - 右值对象优先匹配
&&
限定的函数 - 如果没有匹配的 ref-qualifier,会尝试无 ref-qualifier 的函数(如果存在)
- const/volatile 限定符与 ref-qualifier 可以组合
struct A {void f() & { std::cout << "lvalue\n"; }void f() && { std::cout << "rvalue\n"; }
};A a;
a.f(); // 输出 "lvalue"
std::move(a).f(); // 输出 "rvalue"
A().f(); // 输出 "rvalue"
4. 与 const/volatile 的组合
ref-qualifier 必须放在 cv-qualifier 之后:
struct B {void g() const &; // const 左值void g() &; // 非 const 左值void g() const &&; // const 右值void g() &&; // 非 const 右值
};
重载决议优先级(从高到低):
- 非 const 右值
- const 右值
- 非 const 左值
- const 左值
5. 与模板的结合
ref-qualifier 可以与模板参数结合,用于实现完美转发:
template<typename T>
struct Wrapper {T value;template<typename U>Wrapper(U&& u) : value(std::forward<U>(u)) {}// 只有当 *this 是右值时,才返回右值引用T&& get() && { return std::move(value); }// 当 *this 是左值时,返回左值引用T& get() & { return value; }
};
6. 与完美转发的关系
ref-qualifier 常用于实现成员函数的完美转发,避免不必要的拷贝:
struct Builder {std::string result;// 允许链式调用Builder& operator<<(const std::string& s) & {result += s;return *this;}// 右值版本:不返回引用,避免悬垂引用Builder operator<<(const std::string& s) && {Builder tmp = std::move(*this);tmp.result += s;return tmp;}
};
7. 与继承和虚函数
ref-qualifier 参与函数签名,因此会影响虚函数覆盖:
struct Base {virtual void foo() &;virtual void foo() &&;
};struct Derived : Base {void foo() & override;void foo() && override;
};
注意:如果基类函数有 ref-qualifier,派生类覆盖时必须使用相同的 ref-qualifier。
8. 实用场景
8.1 阻止对临时对象调用某些函数
struct File {FILE* fp;File(const char* name) { fp = fopen(name, "r"); }~File() { if (fp) fclose(fp); }// 禁止对临时文件对象调用 read()size_t read() && = delete;size_t read() & {// 读取文件内容return 0;}
};
8.2 优化返回值
struct Matrix {std::vector<double> data;// 右值版本可以直接移动内部数据std::vector<double> get_data() && {return std::move(data);}// 左值版本只能返回拷贝std::vector<double> get_data() & {return data;}
};
8.3 实现流式接口
struct Logger {std::stringstream ss;Logger& log(const std::string& msg) & {ss << msg;return *this;}std::string log(const std::string& msg) && {ss << msg;return ss.str();}
};
9. 陷阱与注意事项
-
ref-qualifier 是函数签名的一部分
- 这意味着
void f() &
和void f()
是两个不同的重载函数
- 这意味着
-
ref-qualifier 不能用于静态成员函数
- 静态成员函数不属于任何对象实例,因此不能有 ref-qualifier
-
不要滥用
&&
限定符- 过度使用可能导致代码可读性下降
- 只在需要区分左值/右值行为时使用
-
注意 const 与 ref-qualifier 的组合
const&
和&&
是不同的限定符,会影响重载决议
睡眠等同于希望。每次醒来都是一个新的开始,一个新的希望。 —E·M·齐奥朗