第17章 运算符重载 运算符重载基础 运算符重载的概念与原理 运算符重载是C++的核心特性之一,它允许用户为自定义类型定义运算符的行为,使得自定义类型可以像内置类型一样使用运算符。运算符重载本质上是函数重载的一种特殊形式,通过定义特殊的成员函数或非成员函数来实现。
运算符重载的语法规则 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Complex {private : double real; double imag; public : Complex (double r = 0.0 , double i = 0.0 ) : real (r), imag (i) {} Complex operator +(const Complex& other) const { return Complex (real + other.real, imag + other.imag); } Complex& operator =(const Complex& other) { if (this != &other) { real = other.real; imag = other.imag; } return *this ; } }; Complex operator -(const Complex& lhs, const Complex& rhs) { return Complex (lhs.getReal () - rhs.getReal (), lhs.getImag () - rhs.getImag ()); } std::ostream& operator <<(std::ostream& os, const Complex& c) { os << c.getReal () << " + " << c.getImag () << "i" ; return os; }
可重载与不可重载的运算符 可重载的运算符 说明 可重载形式 +, -, *, /, %算术运算符 成员函数或非成员函数 ++, --递增/递减运算符 成员函数(推荐) ==, !=, <, >, <=, >=比较运算符 通常为非成员函数 =, +=, -=, *=, /=, %=赋值运算符 必须为成员函数 <<, >>流运算符 必须为非成员函数 &&, `,!`[]下标运算符 必须为成员函数 ()函数调用运算符 必须为成员函数 ->, ->*指针运算符 必须为成员函数 new, delete内存管理运算符 全局函数或类的静态成员函数
不可重载的运算符 :
. (成员访问运算符).* (成员指针访问运算符):: (作用域解析运算符)?: (条件运算符)sizeof (大小运算符)typeid (类型信息运算符)const_cast, dynamic_cast, reinterpret_cast, static_cast (类型转换运算符)运算符重载的高级实现 一元运算符重载 一元运算符重载需要特别注意前缀和后缀形式的区别,尤其是递增和递减运算符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Counter {private : int value; public : Counter (int v = 0 ) : value (v) {} Counter& operator ++() { ++value; return *this ; } Counter operator ++(int ) { Counter temp = *this ; ++value; return temp; } Counter& operator --() { --value; return *this ; } Counter operator --(int ) { Counter temp = *this ; --value; return temp; } Counter operator -() const { return Counter (-value); } bool operator !() const { return value == 0 ; } };
复合赋值运算符重载 复合赋值运算符通常应该返回左侧对象的引用,以保持与内置类型的行为一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 class Vector {private : double x, y, z; public : Vector (double x = 0.0 , double y = 0.0 , double z = 0.0 ) : x (x), y (y), z (z) {} Vector& operator +=(const Vector& other) { x += other.x; y += other.y; z += other.z; return *this ; } Vector& operator -=(const Vector& other) { x -= other.x; y -= other.y; z -= other.z; return *this ; } Vector& operator *=(double scalar) { x *= scalar; y *= scalar; z *= scalar; return *this ; } Vector& operator /=(double scalar) { if (scalar != 0 ) { x /= scalar; y /= scalar; z /= scalar; } return *this ; } }; Vector operator +(Vector lhs, const Vector& rhs) { lhs += rhs; return lhs; } Vector operator -(Vector lhs, const Vector& rhs) { lhs -= rhs; return lhs; } Vector operator *(Vector v, double scalar) { v *= scalar; return v; } Vector operator *(double scalar, Vector v) { v *= scalar; return v; } Vector operator /(Vector v, double scalar) { v /= scalar; return v; }
下标运算符与函数调用运算符 下标运算符和函数调用运算符是两种特殊的运算符,它们必须作为成员函数实现,并且可以被重载为多个版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Matrix {private : std::vector<std::vector<double >> data; size_t rows, cols; public : Matrix (size_t r, size_t c) : rows (r), cols (c), data (r, std::vector <double >(c, 0.0 )) {} std::vector<double >& operator [](size_t row) { return data[row]; } const std::vector<double >& operator [](size_t row) const { return data[row]; } double & operator () (size_t row, size_t col) { return data[row][col]; } const double & operator () (size_t row, size_t col) const { return data[row][col]; } }; Matrix m (3 , 3 ) ;m[0 ][0 ] = 1.0 ; m (1 , 1 ) = 2.0 ;
类型转换运算符 类型转换运算符允许将自定义类型隐式或显式转换为其他类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Rational {private : int numerator; int denominator; void reduce () { int gcd = std::gcd (numerator, denominator); numerator /= gcd; denominator /= gcd; if (denominator < 0 ) { numerator = -numerator; denominator = -denominator; } } public : Rational (int n = 0 , int d = 1 ) : numerator (n), denominator (d) { reduce (); } operator double () const { return static_cast <double >(numerator) / denominator; } explicit operator bool () const { return numerator != 0 ; } }; Rational r (3 , 4 ) ;double d = r; bool b = static_cast <bool >(r);
运算符重载的编译期优化 编译器对运算符重载的处理 从编译器视角看,运算符重载的实现和使用涉及以下关键优化:
内联展开
小型运算符重载函数会被编译器内联,消除函数调用开销 尤其是算术运算符和比较运算符等简单操作 返回值优化(RVO/NRVO)
编译器会优化返回临时对象的运算符,避免不必要的拷贝 例如 operator+ 返回的临时对象 常量传播
对于常量表达式中的运算符重载,编译器可能在编译期计算结果 尤其是使用 constexpr 修饰的运算符 运算符重载的决议
编译器会根据参数类型和数量选择最匹配的运算符重载版本 考虑隐式类型转换和cv限定符 constexpr 运算符 C++11引入的 constexpr 关键字可以应用于运算符重载,使得运算符在编译期即可计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Point {private : int x, y; public : constexpr Point (int x = 0 , int y = 0 ) : x(x), y(y) { } constexpr int getX () const { return x; } constexpr int getY () const { return y; } constexpr Point operator +(const Point& other) const { return Point (x + other.x, y + other.y); } constexpr Point& operator +=(const Point& other) { x += other.x; y += other.y; return *this ; } }; constexpr Point p1 (1 , 2 ) ;constexpr Point p2 (3 , 4 ) ;constexpr Point p3 = p1 + p2; static_assert (p3. getX () == 4 && p3. getY () == 6 , "编译期计算错误" );
移动语义与运算符重载 移动语义可以显著提高运算符重载的性能,尤其是对于返回值较大的运算符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 class String {private : char * data; size_t length; size_t capacity; void allocate (size_t cap) { data = new char [cap]; capacity = cap; } public : String (const char * str = "" ) { length = std::strlen (str); capacity = length; allocate (capacity + 1 ); std::memcpy (data, str, length + 1 ); } ~String () { delete [] data; } String (String&& other) noexcept : data (other.data), length (other.length), capacity (other.capacity) { other.data = nullptr ; other.length = 0 ; other.capacity = 0 ; } String& operator =(String&& other) noexcept { if (this != &other) { delete [] data; data = other.data; length = other.length; capacity = other.capacity; other.data = nullptr ; other.length = 0 ; other.capacity = 0 ; } return *this ; } String operator +(const String& other) const { String result; result.length = length + other.length; result.capacity = result.length; result.allocate (result.capacity + 1 ); std::memcpy (result.data, data, length); std::memcpy (result.data + length, other.data, other.length + 1 ); return result; } String& operator +=(const String& other) { size_t newLength = length + other.length; if (newLength > capacity) { char * newData = new char [newLength + 1 ]; std::memcpy (newData, data, length); delete [] data; data = newData; capacity = newLength; } std::memcpy (data + length, other.data, other.length + 1 ); length = newLength; return *this ; } }; String s1 ("Hello" ) ;String s2 (" World" ) ;String s3 = s1 + s2;
运算符重载的性能优化 性能优化策略 使用引用传递
对于参数,优先使用 const& 传递,避免不必要的拷贝 对于返回值,根据情况选择值返回(利用RVO)或引用返回 实现复合赋值运算符
复合赋值运算符通常比对应的二元运算符更高效 二元运算符可以基于复合赋值运算符实现,减少代码重复 利用移动语义
对于返回较大对象的运算符,确保移动语义被正确实现 使用 noexcept 标记不抛出异常的移动操作 避免不必要的内存分配
对于字符串、容器等类型,预先分配足够的内存 实现小对象优化(SSO)减少内存分配 使用内联函数
对于简单的运算符重载,使用 inline 关键字或让编译器自动内联 考虑 constexpr
对于可以在编译期计算的运算符,使用 constexpr 修饰 性能比较:成员函数 vs 非成员函数 运算符类型 推荐实现方式 理由 赋值运算符 成员函数 必须为成员函数,且左侧操作数为类实例 下标运算符 成员函数 必须为成员函数,且需要访问类的内部状态 函数调用运算符 成员函数 必须为成员函数,且需要访问类的内部状态 流运算符 非成员函数 需要左侧操作数为流对象,而非类实例 算术运算符 非成员函数 支持交换律,如 1 + obj 和 obj + 1 比较运算符 非成员函数 支持交换律,且对称性更好
案例:高效字符串类的运算符重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 class String {private : enum { SSO_CAPACITY = 15 }; union { char smallBuffer[SSO_CAPACITY + 1 ]; char * largeBuffer; } buffer; size_t length; bool isSmall; void copyFrom (const char * str, size_t len) { if (len <= SSO_CAPACITY) { isSmall = true ; std::memcpy (buffer.smallBuffer, str, len + 1 ); } else { isSmall = false ; buffer.largeBuffer = new char [len + 1 ]; std::memcpy (buffer.largeBuffer, str, len + 1 ); } length = len; } public : String (const char * str = "" ) { copyFrom (str, std::strlen (str)); } ~String () { if (!isSmall) { delete [] buffer.largeBuffer; } } String (const String& other) { copyFrom (other.c_str (), other.length); } String (String&& other) noexcept { if (other.isSmall) { isSmall = true ; std::memcpy (buffer.smallBuffer, other.buffer.smallBuffer, other.length + 1 ); } else { isSmall = false ; buffer.largeBuffer = other.buffer.largeBuffer; other.buffer.largeBuffer = nullptr ; } length = other.length; other.length = 0 ; } String& operator =(String other) noexcept { swap (*this , other); return *this ; } friend void swap (String& lhs, String& rhs) noexcept { using std::swap; swap (lhs.length, rhs.length); swap (lhs.isSmall, rhs.isSmall); if (lhs.isSmall && rhs.isSmall) { std::swap_ranges (lhs.buffer.smallBuffer, lhs.buffer.smallBuffer + std::max (lhs.length, rhs.length) + 1 , rhs.buffer.smallBuffer); } else if (!lhs.isSmall && !rhs.isSmall) { swap (lhs.buffer.largeBuffer, rhs.buffer.largeBuffer); } else if (lhs.isSmall && !rhs.isSmall) { char * temp = rhs.buffer.largeBuffer; std::memcpy (lhs.buffer.smallBuffer, rhs.buffer.smallBuffer, rhs.length + 1 ); rhs.buffer.largeBuffer = lhs.buffer.largeBuffer; lhs.buffer.largeBuffer = temp; lhs.isSmall = false ; rhs.isSmall = true ; } else { swap (rhs, lhs); } } String operator +(const String& other) const { String result; size_t newLength = length + other.length; if (newLength <= SSO_CAPACITY) { result.isSmall = true ; std::memcpy (result.buffer.smallBuffer, c_str (), length); std::memcpy (result.buffer.smallBuffer + length, other.c_str (), other.length + 1 ); } else { result.isSmall = false ; result.buffer.largeBuffer = new char [newLength + 1 ]; std::memcpy (result.buffer.largeBuffer, c_str (), length); std::memcpy (result.buffer.largeBuffer + length, other.c_str (), other.length + 1 ); } result.length = newLength; return result; } String& operator +=(const String& other) { size_t newLength = length + other.length; if (newLength <= SSO_CAPACITY) { std::memcpy (isSmall ? buffer.smallBuffer + length : buffer.largeBuffer + length, other.c_str (), other.length + 1 ); } else if (isSmall) { char * newBuffer = new char [newLength + 1 ]; std::memcpy (newBuffer, buffer.smallBuffer, length); std::memcpy (newBuffer + length, other.c_str (), other.length + 1 ); buffer.largeBuffer = newBuffer; isSmall = false ; } else { char * newBuffer = new char [newLength + 1 ]; std::memcpy (newBuffer, buffer.largeBuffer, length); std::memcpy (newBuffer + length, other.c_str (), other.length + 1 ); delete [] buffer.largeBuffer; buffer.largeBuffer = newBuffer; } length = newLength; return *this ; } char & operator [](size_t index) { return isSmall ? buffer.smallBuffer[index] : buffer.largeBuffer[index]; } const char & operator [](size_t index) const { return isSmall ? buffer.smallBuffer[index] : buffer.largeBuffer[index]; } const char * c_str () const { return isSmall ? buffer.smallBuffer : buffer.largeBuffer; } size_t size () const { return length; } }; bool operator ==(const String& lhs, const String& rhs) { if (lhs.size () != rhs.size ()) return false ; return std::strcmp (lhs.c_str (), rhs.c_str ()) == 0 ; } bool operator !=(const String& lhs, const String& rhs) { return !(lhs == rhs); } bool operator <(const String& lhs, const String& rhs) { return std::strcmp (lhs.c_str (), rhs.c_str ()) < 0 ; } std::ostream& operator <<(std::ostream& os, const String& str) { return os << str.c_str (); }
运算符重载的最佳实践 设计原则 保持语义一致
运算符的行为应该与内置类型的对应运算符一致 例如,+ 应该表示加法,而不是其他操作 遵循最小惊讶原则
运算符的行为应该符合用户的预期 避免重载运算符以执行与名称不符的操作 考虑对称性
对于二元运算符,考虑是否需要支持交换律 例如,1 + obj 和 obj + 1 应该都能工作 注意异常安全性
确保运算符重载在异常情况下不会泄露资源 使用RAII和拷贝交换 idiom 提高异常安全性 避免过度重载
只重载有明确语义的运算符 避免为了语法糖而重载运算符 常见陷阱与解决方案 忘记返回值
例如,赋值运算符应该返回 *this 的引用 解决方案:确保所有运算符都有正确的返回类型 忘记处理自赋值
例如,赋值运算符中没有检查 this == &other 解决方案:在赋值运算符中添加自赋值检查 返回局部对象的引用
例如,operator+ 返回局部变量的引用 解决方案:返回值而不是引用,或确保返回的引用指向有效的对象 重载逻辑运算符时忘记短路求值
例如,operator&& 和 operator|| 没有实现短路求值 解决方案:意识到逻辑运算符的重载会失去短路求值特性,或使用模板技巧模拟 过度使用隐式类型转换
例如,定义了多个隐式类型转换运算符 解决方案:对于可能引起歧义的转换,使用 explicit 关键字 现代C++中的运算符重载 C++11 特性
explicit 类型转换运算符移动语义和 noexcept 委托构造函数减少代码重复 C++14 特性
泛型 lambda 表达式简化运算符重载 变量模板用于编译期计算 C++17 特性
结构化绑定简化返回多个值的运算符 constexpr if 用于编译期条件分支C++20 特性
概念(Concepts)用于约束运算符重载的参数类型 协程(Coroutines)可能影响某些运算符的实现 运算符重载的实际应用 应用场景 数值类型
复数、分数、向量、矩阵等 需要支持算术运算和比较运算 字符串类
需要支持连接、比较等操作 通常实现 +, +=, ==, !=, < 等运算符 容器类
自定义容器需要支持下标访问、迭代等 实现 [], ==, != 等运算符 智能指针
函数对象和lambda表达式
需要支持函数调用操作 实现 operator() 运算符 迭代器
需要支持指针-like操作 实现 ++, --, *, -> 等运算符 自定义字面量
C++11+ 支持自定义字面量运算符 例如,实现 ""_km 字面量表示千米 案例:矩阵类的运算符重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 class Matrix {private : std::vector<std::vector<double >> data; size_t rows, cols; public : Matrix (size_t r, size_t c) : rows (r), cols (c), data (r, std::vector <double >(c, 0.0 )) {} Matrix (std::initializer_list<std::initializer_list<double >> init) { rows = init.size (); if (rows > 0 ) { cols = init.begin ()->size (); data.resize (rows, std::vector <double >(cols, 0.0 )); size_t i = 0 ; for (auto & row : init) { size_t j = 0 ; for (auto val : row) { if (j < cols) { data[i][j] = val; } ++j; } ++i; } } } std::vector<double >& operator [](size_t row) { return data[row]; } const std::vector<double >& operator [](size_t row) const { return data[row]; } double & operator () (size_t row, size_t col) { return data[row][col]; } const double & operator () (size_t row, size_t col) const { return data[row][col]; } Matrix& operator +=(const Matrix& other) { if (rows != other.rows || cols != other.cols) { throw std::invalid_argument ("Matrix dimensions mismatch" ); } for (size_t i = 0 ; i < rows; ++i) { for (size_t j = 0 ; j < cols; ++j) { data[i][j] += other.data[i][j]; } } return *this ; } Matrix& operator -=(const Matrix& other) { if (rows != other.rows || cols != other.cols) { throw std::invalid_argument ("Matrix dimensions mismatch" ); } for (size_t i = 0 ; i < rows; ++i) { for (size_t j = 0 ; j < cols; ++j) { data[i][j] -= other.data[i][j]; } } return *this ; } Matrix& operator *=(const Matrix& other) { if (cols != other.rows) { throw std::invalid_argument ("Matrix dimensions mismatch for multiplication" ); } Matrix result (rows, other.cols) ; for (size_t i = 0 ; i < rows; ++i) { for (size_t j = 0 ; j < other.cols; ++j) { for (size_t k = 0 ; k < cols; ++k) { result (i, j) += data[i][k] * other.data[k][j]; } } } *this = std::move (result); return *this ; } Matrix& operator *=(double scalar) { for (size_t i = 0 ; i < rows; ++i) { for (size_t j = 0 ; j < cols; ++j) { data[i][j] *= scalar; } } return *this ; } size_t getRows () const { return rows; } size_t getCols () const { return cols; } }; Matrix operator +(Matrix lhs, const Matrix& rhs) { lhs += rhs; return lhs; } Matrix operator -(Matrix lhs, const Matrix& rhs) { lhs -= rhs; return lhs; } Matrix operator *(Matrix lhs, const Matrix& rhs) { lhs *= rhs; return lhs; } Matrix operator *(Matrix m, double scalar) { m *= scalar; return m; } Matrix operator *(double scalar, Matrix m) { m *= scalar; return m; } std::ostream& operator <<(std::ostream& os, const Matrix& m) { for (size_t i = 0 ; i < m.getRows (); ++i) { os << "[" ; for (size_t j = 0 ; j < m.getCols (); ++j) { os << m (i, j); if (j < m.getCols () - 1 ) os << ", " ; } os << "]" << std::endl; } return os; }
案例:智能指针的运算符重载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 template <typename T>class SmartPtr {private : T* ptr; size_t * refCount; void release () { if (refCount) { if (--(*refCount) == 0 ) { delete ptr; delete refCount; } } } public : explicit SmartPtr (T* p = nullptr ) : ptr(p), refCount(p ? new size_t(1 ) : nullptr) { } SmartPtr (const SmartPtr& other) : ptr (other.ptr), refCount (other.refCount) { if (refCount) { ++(*refCount); } } SmartPtr (SmartPtr&& other) noexcept : ptr (other.ptr), refCount (other.refCount) { other.ptr = nullptr ; other.refCount = nullptr ; } ~SmartPtr () { release (); } SmartPtr& operator =(const SmartPtr& other) { if (this != &other) { release (); ptr = other.ptr; refCount = other.refCount; if (refCount) { ++(*refCount); } } return *this ; } SmartPtr& operator =(SmartPtr&& other) noexcept { if (this != &other) { release (); ptr = other.ptr; refCount = other.refCount; other.ptr = nullptr ; other.refCount = nullptr ; } return *this ; } T& operator *() const { return *ptr; } T* operator ->() const { return ptr; } explicit operator bool () const { return ptr != nullptr ; } bool operator ==(const SmartPtr& other) const { return ptr == other.ptr; } bool operator !=(const SmartPtr& other) const { return !(*this == other); } };
高级运算符重载技术 模板类的运算符重载 模板类的运算符重载需要特别注意模板参数的推导和特化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 template <typename T>class Vector {private : T* data; size_t size; size_t capacity; public : Vector () : data (nullptr ), size (0 ), capacity (0 ) {} explicit Vector (size_t n, const T& value = T()) : size(n), capacity(n) { data = new T[capacity]; for (size_t i = 0 ; i < size; ++i) { data[i] = value; } } ~Vector () { delete [] data; } T& operator [](size_t index) { return data[index]; } const T& operator [](size_t index) const { return data[index]; } Vector& operator +=(const Vector& other) { if (size != other.size) { throw std::invalid_argument ("Vector sizes mismatch" ); } for (size_t i = 0 ; i < size; ++i) { data[i] += other.data[i]; } return *this ; } size_t getSize () const { return size; } }; template <typename T>Vector<T> operator +(Vector<T> lhs, const Vector<T>& rhs) { lhs += rhs; return lhs; } template <typename T>std::ostream& operator <<(std::ostream& os, const Vector<T>& v) { os << "[" ; for (size_t i = 0 ; i < v.getSize (); ++i) { os << v[i]; if (i < v.getSize () - 1 ) os << ", " ; } os << "]" ; return os; }
完美转发与运算符参数 在运算符重载中,完美转发可以提高性能,特别是对于需要转发参数的运算符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class String { public : template <typename ... Args> String operator +(Args&&... args) const { String result = *this ; result.append (std::forward<Args>(args)...); return result; } private : void append () {} template <typename T, typename ... Args> void append (T&& arg, Args&&... args) { append (std::forward<T>(arg)); append (std::forward<Args>(args)...); } void append (const String& other) { *this += other; } void append (String&& other) { if (isSmall && other.isSmall) { size_t newLength = length + other.length; if (newLength <= SSO_CAPACITY) { std::memcpy (buffer.smallBuffer + length, other.buffer.smallBuffer, other.length + 1 ); length = newLength; other.length = 0 ; } else { *this += std::move (other); } } else { *this += std::move (other); } } void append (const char * str) { String temp (str) ; *this += temp; } void append (char c) { String temp (1 , c) ; *this += temp; } };
运算符重载的性能基准测试 为了评估不同运算符重载实现的性能,可以使用基准测试框架进行测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <benchmark/benchmark.h> static void BM_StringConcat (benchmark::State& state) { String s1 ("Hello, " ) ; String s2 ("world!" ) ; for (auto _ : state) { String s3 = s1 + s2; benchmark::DoNotOptimize (s3); } } BENCHMARK (BM_StringConcat);static void BM_MatrixMultiply (benchmark::State& state) { size_t size = state.range (0 ); Matrix m1 (size, size) ; Matrix m2 (size, size) ; for (size_t i = 0 ; i < size; ++i) { for (size_t j = 0 ; j < size; ++j) { m1 (i, j) = i + j; m2 (i, j) = i - j; } } for (auto _ : state) { Matrix m3 = m1 * m2; benchmark::DoNotOptimize (m3); } } BENCHMARK (BM_MatrixMultiply)->DenseRange (10 , 50 , 10 );BENCHMARK_MAIN ();
总结 运算符重载是C++中一种强大的特性,它允许用户为自定义类型定义运算符的行为,使得自定义类型可以像内置类型一样使用运算符。通过深入理解运算符重载的机制、实现技巧与性能优化策略,开发者可以编写更加高效、可靠、安全的C++代码,充分发挥C++的性能优势。
关键要点 语义一致性 :运算符的行为应该与内置类型的对应运算符一致,遵循最小惊讶原则。
性能优化 :
使用引用传递减少拷贝开销 利用返回值优化(RVO/NRVO) 实现移动语义提高性能 考虑小对象优化(SSO)减少内存分配 使用 constexpr 支持编译期计算 异常安全性 :
确保运算符重载在异常情况下不会泄露资源 使用RAII和拷贝交换 idiom 提高异常安全性 设计选择 :
对于需要支持交换律的运算符,优先使用非成员函数实现 对于修改对象状态的运算符,使用成员函数实现 对于流运算符等特殊情况,使用非成员函数实现 现代C++特性 :
利用C++11+的移动语义和 noexcept 使用C++14的泛型lambda表达式简化实现 利用C++17的结构化绑定和 constexpr if 考虑C++20的概念(Concepts)约束参数类型 通过合理使用运算符重载,开发者可以创建更加直观、易用且高效的自定义类型,提升代码的可读性和可维护性,同时保持C++的高性能特性。