运算符重载
00:00
运算符重载规则与最佳实践
概述
运算符重载允许为自定义类型重新定义运算符的行为,使得用户定义的类型可以像内置类型一样使用运算符。合理使用运算符重载可以使代码更直观、更符合领域语言的惯例;滥用则会使代码难以理解。运算符重载的核心原则是:重载后的行为应与内置运算符的语义一致。
基础概念
可重载与不可重载的运算符
可重载的运算符包括:+ - * / % ^ & | ~ ! = < > <= >= += -= *= /= %= ^= &= |= << >> >>= <<= == != <=> && || ++ -- ->* -> () [] , new delete 等。
不可重载的运算符:. .* :: ?: sizeof typeid。
成员函数与非成员函数
| 选择原则 | 说明 |
|---|---|
| 必须为成员 | = () [] -> |
| 必须为非成员 | << >>(左操作数是流) |
| 建议为非成员 | 对称运算符(+ == <=>),支持左操作数隐式转换 |
| 建议为成员 | 复合赋值(+= -=),修改自身 |
快速上手
二元算术运算符
class Vector2D {
double x_, y_;
public:
Vector2D(double x = 0, double y = 0) : x_(x), y_(y) {}
// 复合赋值运算符(成员函数)
Vector2D& operator+=(const Vector2D& rhs) {
x_ += rhs.x_;
y_ += rhs.y_;
return *this;
}
// 二元运算符(非成员函数,通过 += 实现)
friend Vector2D operator+(Vector2D lhs, const Vector2D& rhs) {
lhs += rhs;
return lhs;
}
double x() const { return x_; }
double y() const { return y_; }
};
比较运算符(C++20 三路比较)
class Point {
int x_, y_;
public:
Point(int x, int y) : x_(x), y_(y) {}
// C++20: 只需定义 <=> 运算符,编译器自动生成 < <= > >= ==
auto operator<=>(const Point&) const = default;
};
// C++20 之前需要逐一定义
class PointOld {
int x_, y_;
public:
bool operator==(const PointOld& other) const {
return x_ == other.x_ && y_ == other.y_;
}
bool operator<(const PointOld& other) const {
return x_ < other.x_ || (x_ == other.x_ && y_ < other.y_);
}
// 还需要定义 !=, <=, >, >=
};
详细用法
下标运算符
#include <stdexcept>
class IntArray {
int* data_;
size_t size_;
public:
explicit IntArray(size_t n) : data_(new int[n]()), size_(n) {}
~IntArray() { delete[] data_; }
// 非常量版本:可修改元素
int& operator[](size_t index) {
if (index >= size_) throw std::out_of_range("索引越界");
return data_[index];
}
// 常量版本:只读访问
const int& operator[](size_t index) const {
if (index >= size_) throw std::out_of_range("索引越界");
return data_[index];
}
};
IntArray arr(10);
arr[0] = 42; // 调用非常量版本
const IntArray& carr = arr;
int val = carr[0]; // 调用常量版本
函数调用运算符
#include <cmath>
// 函数对象(Functor)
class LinearInterpolator {
double x1_, y1_, x2_, y2_;
public:
LinearInterpolator(double x1, double y1, double x2, double y2)
: x1_(x1), y1_(y1), x2_(x2), y2_(y2) {}
double operator()(double x) const {
double t = (x - x1_) / (x2_ - x1_);
return y1_ + t * (y2_ - y1_);
}
};
// 使用
LinearInterpolator interp(0, 0, 10, 100);
double y = interp(5); // 50.0
流运算符
#include <iostream>
class Fraction {
int numerator_, denominator_;
public:
Fraction(int n, int d) : numerator_(n), denominator_(d) {}
// 输出运算符必须为非成员函数(左操作数是 ostream)
friend std::ostream& operator<<(std::ostream& os, const Fraction& f) {
os << f.numerator_ << "/" << f.denominator_;
return os;
}
// 输入运算符
friend std::istream& operator>>(std::istream& is, Fraction& f) {
char slash;
is >> f.numerator_ >> slash >> f.denominator_;
return is;
}
};
Fraction f(3, 4);
std::cout << f << std::endl; // 输出: 3/4
自增与自减运算符
class Counter {
int value_;
public:
explicit Counter(int v = 0) : value_(v) {}
// 前置 ++:返回引用,可修改
Counter& operator++() {
++value_;
return *this;
}
// 后置 ++:返回旧值的副本,int 参数是区分标志
Counter operator++(int) {
Counter temp = *this;
++value_;
return temp;
}
// 前置 --
Counter& operator--() {
--value_;
return *this;
}
// 后置 --
Counter operator--(int) {
Counter temp = *this;
--value_;
return temp;
}
int value() const { return value_; }
};
Counter c(5);
++c; // c.value() == 6
Counter old = c++; // old.value() == 6, c.value() == 7
类型转换运算符
class SafeBool {
bool value_;
public:
explicit SafeBool(bool v) : value_(v) {}
// C++11: explicit 防止隐式转换
explicit operator bool() const { return value_; }
};
SafeBool sb(true);
if (sb) { /* 正确:显式转换上下文 */ }
// int n = sb; // 错误:explicit 阻止隐式转换
常见场景
智能指针风格的运算符
template<typename T>
class ScopedPtr {
T* ptr_;
public:
explicit ScopedPtr(T* p = nullptr) : ptr_(p) {}
~ScopedPtr() { delete ptr_; }
// 解引用
T& operator*() const { return *ptr_; }
// 箭头运算符
T* operator->() const { return ptr_; }
// 布尔转换
explicit operator bool() const { return ptr_ != nullptr; }
};
ScopedPtr<Widget> p(new Widget);
p->doWork(); // 通过 -> 访问成员
(*p).doWork(); // 通过 * 解引用
if (p) { /* ... */ } // 通过 bool 转换检查
迭代器运算符
template<typename T>
class ArrayIterator {
T* ptr_;
public:
explicit ArrayIterator(T* p) : ptr_(p) {}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
ArrayIterator& operator++() { ++ptr_; return *this; }
ArrayIterator operator++(int) { auto tmp = *this; ++ptr_; return tmp; }
bool operator==(const ArrayIterator& other) const { return ptr_ == other.ptr_; }
bool operator!=(const ArrayIterator& other) const { return ptr_ != other.ptr_; }
};
注意事项
- 运算符重载应保持与内置运算符一致的语义,不要让
+执行减法 - 不要重载
&&、||和,运算符,因为重载版本无法保留内置运算符的短路求值和求值顺序 - 二元对称运算符(如
+、==)应声明为非成员函数,以支持左操作数的隐式转换 - 复合赋值运算符(如
+=)应返回*this的引用 - 后置自增/自减应返回旧值的副本,前置版本应返回引用
- C++20 的
<=>运算符大幅简化了比较运算符的实现
进阶用法
C++20 三路比较运算符
#include <compare>
class Version {
int major_, minor_, patch_;
public:
Version(int maj, int min, int pat) : major_(maj), minor_(min), patch_(pat) {}
// 定义三路比较,自动生成 < <= > >= == !=
std::strong_ordering operator<=>(const Version& other) const {
if (auto cmp = major_ <=> other.major_; cmp != 0) return cmp;
if (auto cmp = minor_ <=> other.minor_; cmp != 0) return cmp;
return patch_ <=> other.patch_;
}
// 也可以使用 default
// auto operator<=>(const Version&) const = default;
};
Version v1(1, 2, 3), v2(1, 2, 4);
bool less = v1 < v2; // true
bool equal = v1 == v2; // false
自定义字面量
#include <chrono>
// 自定义字面量运算符
constexpr std::chrono::milliseconds operator""_ms(unsigned long long value) {
return std::chrono::milliseconds(value);
}
constexpr std::chrono::seconds operator""_s(unsigned long long value) {
return std::chrono::seconds(value);
}
// 使用
auto timeout = 100_ms;
auto delay = 5_s;
std::this_thread::sleep_for(timeout);