operator overloading
操作者超载
为用户定义类型的操作数自定义C++运算符.
句法
重载运算符是功能具有特殊功能名称:
operator op | (1) | |
---|---|---|
operator type | (2) | |
operator new operator new [] | (3) | |
operator delete operator delete [] | (4) | |
operator "" suffix-identifier | (5) | (since C++11) |
op | - | any of the following 38 operators:+ - * / % ˆ & | ~ ! = < > += -= *= /= %= ˆ= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> ( ) |
---|
超载操作员1%29人;
2%29用户定义的转换函数;
3%29分配函数;
4%29解分配函数;
5%29用户定义文字...
过载运算符
当运算符出现在表达,并且它的至少一个操作数具有一个类类型或者枚举类型,然后过载分辨率用于确定在签名与以下内容匹配的所有函数之间调用的用户定义函数:
Expression | As member function | As non-member function | Example |
---|---|---|---|
@a | (a).operator@ ( ) | operator@ (a) | !std::cin calls std::cin.operator!() |
a@b | (a).operator@ (b) | operator@ (a, b) | std::cout << 42 calls std::cout.operator<<(42) |
a=b | (a).operator= (b) | cannot be non-member | std::string s; s = "abc"; calls s.operator=("abc") |
a(b...) | (a).operator()(b...) | cannot be non-member | std::random_device r; auto n = r( calls r.operator()() |
ab | (a).operator | cannot be non-member | std::map<int, int> m; m1 = 2; calls m.operator |
a-> | (a).operator-> ( ) | cannot be non-member | auto p = std::make_unique<S>( p->bar() calls p.operator->() |
a@ | (a).operator@ (0) | operator@ (a, 0) | std::vector<int>::iterator i = v.begin( i++ calls i.operator++(0) |
在这个表中,@是表示所有匹配运算符的占位符:@a中的所有前缀运算符,a中除->以外的所有后缀运算符,除a@b中的==以外的所有infix运算符。
注:过载用户定义的转换函数,,,用户定义文字,,,分配和脱钩看他们各自的文章。
重载运算符%28,但不能使用函数表示法调用内置运算符%29:
二次
std::string str = "Hello, ";
str.operator+=("world" // same as str += "world";
operator<<(operator<<(std::cout, str) , '\n' // same as std::cout << str << '\n';
// (since C++17) except for sequencing
二次
限制
- 操作者
::
28%范围分辨率%29,.
28名成员访问率%29,.*
%28成员通过指向成员%29的指针访问,以及?:
%28三元条件%29不能重载。
- 新的运算符,如**,,,<>,或&|无法被创造。
- 操作者的过载
&&
和||
失去短路评估。
- 操作者过载->必须返回原始指针或通过引用或值%29返回对象%28,该运算符为->反过来又超载了。
- 无法更改运算符的优先级、分组或操作数。
&&, ||, and , (comma) lose their special sequencing properties when overloaded and behave like regular function calls even when they are used without function-call notation. | (until C++17) |
---|
- &&,,,||,和,%28逗号%29失去他们的特长测序性质当重载和行为类似于常规函数调用时,即使它们没有函数调用表示法也是如此。%28直到C++17%29规范实现除上述限制外,该语言不对重载运算符的操作设置其他限制,或者对返回类型%28不参与重载解析%29,但通常,重载运算符的行为应尽可能与内置运算符相似:operator+应该添加,而不是乘以它的参数,operator=预期分配等。相关运算符的行为类似于%28。operator+和operator+=执行相同的加法操作%29。返回类型受到预期使用运算符的表达式的限制:例如,赋值运算符通过引用返回以使写入成为可能。a = b = c = d,因为内置操作符允许这样做。通常重载操作符有以下典型的规范形式:[1]赋值算子赋值运算符%28operator=%29具有特殊属性:见复制分配和移动分配关于细节。正则副本赋值操作符预期为不对自我分配执行任何操作。,并以下列方式退回LHS:二次// assume the object holds reusable storage, such as a heap-allocated buffer mArray T& operator=(const T& other) // copy assignment { if (this != &other) { // self-assignment check expected if (other.size != size) { // storage cannot be reused delete[] mArray; // destroy storage in this size = 0; mArray = nullptr; // preserve invariants in case next line throws mArray = new int[other.size]; // create storage in this size = other.size; } else { // storage can be reused std::copy(other.mArray, other.mArray + other.size, mArray } } return *this; }二次常规移动分配预计将使移出对象处于有效状态。%28,也就是一个不变量为%29的状态,无所事事或至少在自分配时将对象保持在有效状态,并通过引用非Const返回LHS,并且不除:二次T& operator=(T&& other) noexcept // move assignment { if(this != &other) { // no-op on self-move-assignment (delete[]/size=0 also ok) delete[] mArray; // delete this storage mArray = std::exchange(other.mArray, nullptr // leave moved-from in valid state size = std::exchange(other.size, 0 } return *this; }二次在复制分配不能从资源重用中获益的情况下,它不管理堆分配的数组,并且没有%28可能是传递的%29成员,例如成员。std::vector或std::string%29,有一个流行的方便的速记:复制和交换赋值操作符,它通过值%28获取其参数,从而根据参数%29的值类别,作为复制分配和移动分配,与参数交换,并让析构函数清理它。二次T& T::operator=(T arg) noexcept // copy/move constructor is called to construct arg { swap(arg // resources are exchanged between *this and arg return *this; } // destructor of arg is called to release the resources formerly held by *this二次此表单自动提供强例外保证,但禁止资源重用。流提取和插入过载operator>>和operator<<需要一个std::istream&或std::ostream&由于左手参数被称为插入和提取操作符。因为它们将用户定义的类型作为正确的参数%28。b在a@b%29中,它们必须作为非成员来实现。二次std::ostream& operator<<(std::ostream& os, const T& obj) { // write obj to stream return os; } std::istream& operator>>(std::istream& is, T& obj) { // read obj from stream if( /* T could not be constructed */ ) is.setstate(std::ios::failbit return is; }二次这些操作符有时被实现为朋友函数...函数调用算子当用户定义的类重载函数调用运算符时,operator(),它变成了FunctionObject类型。许多标准算法std::sort到std::accumulate接受这类类型的对象来自定义行为。没有特别值得注意的规范形式operator(),但是为了说明用法。二次struct Sum { int sum; Sum() : sum(0) { } void operator()(int n) { sum += n; } }; Sum s = std::for_each(v.begin(), v.end(), Sum()二次增减当表达式中出现后缀增量和递减时,相应的用户定义函数%28operator++或operator--%29是用整数参数调用的。0通常,它是作为T operator++(int),这里的论点被忽略了。后缀增量和递减运算符通常是根据前缀版本实现的:二次struct X { X& operator++() { // actual increment takes place here return *this; } X operator++(int) { X tmp(*this // copy operator++( // pre-increment return tmp; // return old value } };二次虽然标准形式的预增量/预减量返回引用,就像任何操作符重载一样,返回类型是用户定义的;例如,这些操作符的重载std::atomic按价值返回。二进制算术算子二进制运算符通常作为非成员来实现,以保持对称性%28--例如,当添加复数和整数时,如果operator+是复杂类型的成员函数,则仅complex+integer会编译,而不是integer+complex29%。由于每个二进制算术运算符都有一个相应的复合赋值运算符,所以二进制运算符的规范形式是根据它们的复合赋值实现的:二次class X { public: X& operator+=(const X& rhs) // compound assignment (does not need to be a member, { // but often is, to modify the private members) /* addition of rhs to *this takes place here */ return *this; // return the result by reference } // friends defined inside class body are inline and are hidden from non-ADL lookup friend X operator+(X lhs, // passing lhs by value helps optimize chained a+b+c const X& rhs) // otherwise, both parameters may be const references { lhs += rhs; // reuse compound assignment return lhs; // return the result by value (uses move constructor) } };二次关系算子标准算法,如std::sort以及像这样的容器std::set期待operator<默认情况下为用户提供的类型定义,并期望它实现严格的弱排序%28,从而满足Compare概念%29。实现结构严格弱排序的一种惯用方法是使用std::tie*二次struct Record { std::string name; unsigned int floor; double weight; friend bool operator<(const Record& l, const Record& r) { return std::tie(l.name, l.floor, l.weight) < std::tie(r.name, r.floor, r.weight // keep the same order } };二次通常,一次operator<,则其他关系运算符将根据operator<...二次inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ } inline bool operator> (const X& lhs, const X& rhs){ return rhs < lhs; } inline bool operator<=(const X& lhs, const X& rhs){ return !(lhs > rhs } inline bool operator>=(const X& lhs, const X& rhs){ return !(lhs < rhs }二次同样,不等式运算符通常是根据operator==*二次inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ } inline bool operator!=(const X& lhs, const X& rhs){ return !(lhs == rhs }二次当三方比较%28时,例如std::memcmp或std::string::compare提供了%29,所有六个关系运算符都可以通过以下方式表示:二次inline bool operator==(const X& lhs, const X& rhs){ return cmp(lhs,rhs) == 0; } inline bool operator!=(const X& lhs, const X& rhs){ return cmp(lhs,rhs) != 0; } inline bool operator< (const X& lhs, const X& rhs){ return cmp(lhs,rhs) < 0; } inline bool operator> (const X& lhs, const X& rhs){ return cmp(lhs,rhs) > 0; } inline bool operator<=(const X& lhs, const X& rhs){ return cmp(lhs,rhs) <= 0; } inline bool operator>=(const X& lhs, const X& rhs){ return cmp(lhs,rhs) >= 0; }二次数组下标算子用户定义的类,提供类似数组的访问,允许读写,通常定义两个重载operator[]*Const和non-Const变体:二次struct T { value_t& operator[](std::size_t idx) { return mVector[idx]; } const value_t& operator[](std::size_t idx) const { return mVector[idx]; } };二次如果值类型已知为内置类型,则Const变量应按值返回。如果不希望或不可能直接访问容器的元素,或区分lvaluec[i] = v;和rvaluev = c[i];使用,操作员。[]可能返回代理。例如见std::bitset::operator[]...提供多维数组访问语义,例如实现三维数组访问a[i][j][k] = x;、操作员。[必须返回对2D平面的引用,该平面必须有自己的运算符。[]返回对1D行的引用,该行必须有运算符。[]返回对元素的引用。为了避免这种复杂性,一些库选择重载。operator()相反,使3d访问表达式具有类似Fortran的语法。a(i, j, k) = x;位算术运算符的需求实现的用户定义类和枚举。BitmaskType需要重载按位运算运算符。operator&,,,operator|,,,operator^,,,operator~,,,operator&=,,,operator|=,和operator^=,并且可能会使移位操作符过载。operator<<operator>>,,,operator>>=,和operator<<=规范实现通常遵循上述二进制算术运算符的模式。布尔否定算子操作员operator!通常由用户定义的类重载,这些类要在布尔上下文中使用。这些类还提供了用户定义的转换函数。explicit operator bool()28%见std::basic_ios的标准库示例%29,以及operator!的相反值。operator bool...很少超载的运算符以下运算符很少重载:
- 接线员的地址,
operator&
如果一元&应用于不完全类型的lvalue,而完整类型声明重载operator&
,在C++11%29之前,该行为是未定义的--自C++11%29以来,它定义了重载运算符是否使用%28。由于此运算符可能重载,泛型库将使用std::addressof
若要获取用户定义类型的对象的地址,请执行以下操作。最著名的规范重载操作符示例是microsoft类。CComPtr%28https://msdn.microsoft.com/en-us/Library/31k6d0k7%28V=vs.140%29.aspx%29.在eDSL中使用它的示例可在酒鬼...
- 布尔逻辑运算符,
operator&&
和operator||
.与内置版本不同,过载不能实现短路评估.。与内置版本不同的是,它们不会将左操作数排在右操作数之前。%28在标准库中的C++17%29之前,这些运算符仅重载std::valarray
...
- 逗号接线员,
operator,
.与内置版本不同,重载不将其左操作数排在右操作数之前.。%28直到C++17%29,因为这个运算符可能被重载,泛型库使用的表达式如下a,void(),b
而不是a,b
对用户定义类型的表达式进行顺序执行。Boost库使用operator,
在助推,分配,,,酒鬼,以及其他图书馆。数据库访问库沙基也是过载operator,
...
- 成员通过指向成员的指针进行访问。operator->*没有具体的缺点超载这个操作者,但它很少在实践中使用。有人建议它可能是智能指针接口,而事实上,行为者以这种身份使用加油。凤凰城.在电子数据交换系统中比较常见,例如cpp.反应...
例
二次
#include <iostream>
class Fraction
{
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b }
int n, d;
public:
Fraction(int n, int d = 1) : n(n/gcd(n, d)), d(d/gcd(n, d)) { }
int num() const { return n; }
int den() const { return d; }
Fraction& operator*=(const Fraction& rhs)
{
int new_n = n * rhs.n/gcd(n * rhs.n, d * rhs.d
d = d * rhs.d/gcd(n * rhs.n, d * rhs.d
n = new_n;
return *this;
}
};
std::ostream& operator<<(std::ostream& out, const Fraction& f)
{
return out << f.num() << '/' << f.den() ;
}
bool operator==(const Fraction& lhs, const Fraction& rhs)
{
return lhs.num() == rhs.num() && lhs.den() == rhs.den(
}
bool operator!=(const Fraction& lhs, const Fraction& rhs)
{
return !(lhs == rhs
}
Fraction operator*(Fraction lhs, const Fraction& rhs)
{
return lhs *= rhs;
}
int main()
{
Fraction f1(3, 8), f2(1, 2), f3(10, 2
std::cout << f1 << " * " << f2 << " = " << f1 * f2 << '\n'
<< f2 << " * " << f3 << " = " << f2 * f3 << '\n'
<< 2 << " * " << f1 << " = " << 2 * f1 << '\n';
}
二次
产出:
二次
3/8 * 1/2 = 3/16
1/2 * 5/1 = 5/2
2 * 3/8 = 3/4
二次
缺陷报告
以下行为更改缺陷报告追溯应用于先前发布的C++标准。
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
CWG 1458 | C++11 | taking address of incomplete type that overloads address-of was undefined behavior | the behavior is only unspecified |
另见
- 运算符优先
- 替代运算符语法
公共算子
*。
分配增量递减算术逻辑比较成员访问其他
a=b a+=b a-=b a%2A=b a/=b a%=b a&=b a=b a^=b a<=b a>>=b.+a-a+a-+a-+a-a+b a-b a%2Ab a/b a%b~a&b ab^b a<<b a>>b.%21 a&b a&b ab a=b a%21=b a<b a>b a<=b a>=b a乙%2AA&A->b A.B a->%2Ab a.%2AA%28...%29 a,b?*
特殊运算符
静态[医]强制转换将一种类型转换为另一种相关类型动态。[医]继承层次结构中的强制转换[医]强制转换添加或删除cv限定符,重新解释[医]CAST将类型转换为不相关的类型C风格的强制转换通过混合静态方式将一种类型转换为另一种类型[医]卡斯特[医]重释[医]强制转换新创建具有动态存储持续时间的对象,删除删除以前由新表达式创建的对象,并释放获得的内存区域大小查询类型的大小...查询参数Pack%28的大小,因为C++11%29 Tyid查询类型no的类型信息,除了检查。表达式可以抛出异常%28,因为C++11%29查询对齐要求类型为%28,因为C++11%29。
参考文献
- 操作者超载关于StackOverflow C++常见问题
© cppreference.com
在CreativeCommonsAttribution下授权-ShareAlike未移植许可v3.0。