C++运算符重载的基本规则和惯用法 技术背景 在C++中,运算符重载允许程序员为自定义类型重新定义运算符的行为,使得代码更加直观和易于理解。然而,运算符重载需要遵循一定的规则和惯用法,以确保代码的正确性和可维护性。
实现步骤 1. 遵循基本规则 避免不明确的重载 :当运算符的含义不明确且有争议时,不应进行重载,而应提供一个命名良好的函数。保持运算符的常见语义 :重载运算符时,应遵循其在常规使用中的语义,避免让用户产生意外的结果。提供相关操作 :如果类型支持某个运算符,用户通常期望能使用与其相关的其他运算符,因此应提供完整的相关操作。2. 选择成员或非成员函数 必须为成员函数的运算符 :[]
、()
、=
、->
等运算符必须实现为成员函数。通常为非成员函数的运算符 :输入和输出运算符 <<
和 >>
通常需要实现为非成员函数,因为其左操作数是标准库中的流类,无法添加成员函数。其他运算符的选择规则 :一元运算符通常实现为成员函数。 对两个操作数同等对待的二元运算符通常实现为非成员函数。 对两个操作数处理方式不同(通常会修改左操作数)的二元运算符,若需要访问操作数的私有部分,可实现为左操作数类型的成员函数。 3. 重载常见运算符 赋值运算符 :使用复制 - 交换惯用法实现赋值运算符。1 2 3 4 5 X& X::operator =(X rhs) { swap (rhs); return *this ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 std::ostream& operator <<(std::ostream& os, const T& obj) { return os; } std::istream& operator >>(std::istream& is, T& obj) { if ( ) is.setstate (std::ios::failbit); return is; }
1 2 3 4 5 6 struct X { int operator () (const std::string& y) { return ; } };
比较运算符 :在C++20中,可通过默认 operator<=>
来重载所有比较运算符。1 2 3 4 5 6 #include <compare> struct X { friend auto operator <=>(const X&, const X&) = default ; };
逻辑运算符 :一元前缀否定 !
通常实现为成员函数,二元逻辑运算符 ||
和 &&
通常实现为非成员函数,但很少有合理的使用场景。算术运算符 :一元算术运算符:重载递增和递减运算符时,应同时实现前缀和后缀版本。 1 2 3 4 5 6 7 8 9 10 11 12 13 struct X { X& operator ++() { return *this ; } X operator ++(int ) { X tmp (*this ) ; operator ++(); return tmp; } };
- 二元算术运算符:实现 `+` 时,通常基于 `+=` 来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct X { X& operator +=(const X& rhs) { return *this ; } };inline X operator +(const X& lhs, const X& rhs) { X result = lhs; result += rhs; return result; }
下标运算符 :必须实现为类成员,通常提供常量和非常量版本。1 2 3 4 5 struct X { value_type& operator [](index_type idx); const value_type& operator [](index_type idx) const ; };
指针类类型的运算符 :重载一元前缀解引用运算符 *
和二元中缀指针成员访问运算符 ->
,通常需要常量和非常量版本。1 2 3 4 5 6 struct my_ptr { value_type& operator *(); const value_type& operator *() const ; value_type* operator ->(); const value_type* operator ->() const ; };
4. 转换运算符 隐式转换运算符 :允许编译器隐式地将用户定义类型的值转换为其他类型,但可能会导致意外的结果。1 2 3 4 5 6 class my_string {public : operator const char *() const {return data_;} private : const char * data_; };
显式转换运算符 :避免了隐式转换带来的问题,需要使用 static_cast
等显式转换。1 2 3 4 5 6 class my_string {public : explicit operator const char *() const {return data_;}private : const char * data_; };
5. 重载 new
和 delete
运算符 基本规则 :重载 new
和 delete
运算符通常是为了解决性能问题和内存约束,应同时重载匹配的 operator delete
。1 2 3 4 void * operator new (std::size_t ) throw (std::bad_alloc) ; void operator delete (void *) throw () ; void * operator new [](std::size_t ) throw (std::bad_alloc); void operator delete [](void *) throw ();
1 2 3 4 5 6 7 8 class X { };char buffer[ sizeof (X) ];void f () { X* p = new (buffer) X (); p->~X (); }
类特定的 new
和 delete
:可以为特定类重载 new
和 delete
以优化内存管理。1 2 3 4 5 6 7 8 9 class my_class { public : void * operator new (std::size_t ) ; void operator delete (void *) ; void * operator new [](std::size_t ); void operator delete [](void *); };
最佳实践 遵循规则 :严格遵循运算符重载的基本规则,确保代码的正确性和可维护性。使用默认比较 :在C++20中,优先使用默认比较运算符,减少样板代码。考虑性能 :在实现运算符重载时,考虑性能因素,如优先使用 +=
而不是 +
。常见问题 运算符选择错误 :选择成员函数或非成员函数实现运算符时出错,导致代码无法编译或行为不符合预期。语义不一致 :重载运算符时,违反了运算符的常见语义,使代码难以理解和维护。遗漏相关操作 :没有提供完整的相关操作,导致用户代码无法正常使用。