名稱空間
變體
操作

運算子過載

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

為使用者定義型別的運算元定製 C++ 運算子。

目錄

[編輯] 語法

運算子函式 是具有特殊函式名稱的函式

operator op (1)
operator new
operator new []
(2)
operator delete
operator delete []
(3)
operator co_await (4) (C++20 起)
op - 以下任一運算子:+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= <=>(C++20起) && || ++ -- , ->* -> () []
1) 過載的標點符號運算子。
4) 過載的 co_await 運算子,用於 co_await 表示式

非標點運算子的行為在其各自頁面中描述。除非另有說明,本頁面中的其餘描述不適用於這些函式。

[編輯] 解釋

當運算子出現在表示式中,並且其運算元至少有一個是類型別列舉型別時,則使用過載決議來確定要呼叫的使用者定義函式,該函式是從所有簽名匹配以下條件的函式中選擇的

表示式 作為成員函式 作為非成員函式 示例
@a (a).operator@ ( ) operator@ (a) !std::cin 呼叫 std::cin.operator!()
a@b (a).operator@ (b) operator@ (a, b) std::cout << 42 呼叫 std::cout.operator<<(42)
a=b (a).operator= (b) 不能為非成員 給定 std::string s;s = "abc"; 呼叫 s.operator=("abc")
a(b...) (a).operator()(b...) 不能為非成員 給定 std::random_device r;auto n = r(); 呼叫 r.operator()()
a[b...] (a).operator[](b...) 不能為非成員 給定 std::map<int, int> m;m[1] = 2; 呼叫 m.operator[](1)
a-> (a).operator->( ) 不能為非成員 給定 std::unique_ptr<S> p;p->bar() 呼叫 p.operator->()
a@ (a).operator@ (0) operator@ (a, 0) 給定 std::vector<int>::iterator i;i++ 呼叫 i.operator++(0)

在此表格中,@ 是一個佔位符,表示所有匹配的運算子:@a 中的所有字首運算子,a@ 中除 -> 之外的所有後綴運算子,a@b 中除 = 之外的所有中綴運算子。

此外,對於比較運算子 ==!=<><=>=<=>,過載決議還會考慮重寫的候選 operator==operator<=>

(C++20 起)

過載運算子(而非內建運算子)可以使用函式表示法呼叫

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

靜態過載運算子

作為成員函式的過載運算子可以宣告為靜態。然而,這隻允許用於 operator()operator[]

此類運算子可以使用函式表示法呼叫。然而,當這些運算子出現在表示式中時,它們仍然需要一個類型別的物件。

struct SwapThem
{
    template<typename T>
    static void operator()(T& lhs, T& rhs) 
    {
        std::ranges::swap(lhs, rhs);
    }
 
    template<typename T>
    static void operator[](T& lhs, T& rhs)
    {
        std::ranges::swap(lhs, rhs);
    } 
};
inline constexpr SwapThem swap_them{};
 
void foo()
{
    int a = 1, b = 2;
 
    swap_them(a, b); // OK
    swap_them[a, b]; // OK
 
    SwapThem{}(a, b); // OK
    SwapThem{}[a, b]; // OK
 
    SwapThem::operator()(a, b); // OK
    SwapThem::operator[](a, b); // OK
 
    SwapThem(a, b); // error, invalid construction
    SwapThem[a, b]; // error
}
(C++23 起)

[編輯] 限制

  • 運算子函式必須至少有一個函式引數或隱式物件引數,其型別為類、對類的引用、列舉或對列舉的引用。
  • 運算子 ::(作用域解析)、.(成員訪問)、.*(透過成員指標訪問成員)和 ?:(三元條件)不能被過載。
  • 不能建立新的運算子,例如 **<>&|
  • 不可能改變運算子的優先順序、分組或運算元數量。
  • 運算子 -> 的過載必須返回原始指標,或返回一個物件(透過引用或值),該物件的 -> 運算子又被過載。
  • 運算子 &&|| 的過載會失去短路求值特性。
  • &&||, 被過載時,它們會失去特殊的序列化特性,即使不使用函式呼叫表示法,它們也表現得像常規函式呼叫。
(C++17 前)

[編輯] 規範實現

除了上述限制之外,語言對過載運算子的行為或返回型別(它不參與過載決議)沒有其他限制,但通常,過載運算子預期儘可能與內建運算子的行為相似:operator+ 預期是加法,而不是乘法;operator= 預期是賦值等等。相關運算子預期行為相似(operator+operator+= 執行相同的加法類操作)。返回型別受限於運算子預期使用的表示式:例如,賦值運算子透過引用返回,以使 a = b = c = d 成為可能,因為內建運算子允許這樣做。

常用的過載運算子具有以下典型的規範形式:[1]

[編輯] 賦值運算子

賦值運算子 operator= 具有特殊屬性:詳細資訊請參見複製賦值移動賦值

規範的複製賦值運算子預期對自賦值是安全的,並透過引用返回左值

// copy assignment
T& operator=(const T& other)
{
    // Guard self assignment
    if (this == &other)
        return *this;
 
    // assume *this manages a reusable resource, such as a heap-allocated buffer mArray
    if (size != other.size)           // resource in *this cannot be reused
    {
        temp = new int[other.size];   // allocate resource, if throws, do nothing
        delete[] mArray;              // release resource in *this
        mArray = temp;
        size = other.size;
    }
 
    std::copy(other.mArray, other.mArray + other.size, mArray);
    return *this;
}

規範的移動賦值預期將移動源物件置於有效狀態(即,類不變數保持不變的狀態),並且在自賦值時不做任何事情或至少將物件置於有效狀態,並透過對非 const 的引用返回左值,並且為 noexcept。

// move assignment
T& operator=(T&& other) noexcept
{
    // Guard self assignment
    if (this == &other)
        return *this; // delete[]/size=0 would also be ok
 
    delete[] mArray;                               // release resource in *this
    mArray = std::exchange(other.mArray, nullptr); // leave other in valid state
    size = std::exchange(other.size, 0);
    return *this;
}
(C++11 起)

在複製賦值不能從資源重用中受益(它不管理堆分配的陣列,並且沒有(可能是傳遞的)成員這樣做,例如 std::vectorstd::string 成員)的情況下,有一種流行的便捷簡寫:複製並交換賦值運算子,它透過值接收其引數(因此根據引數的值類別,既可作為複製賦值又可作為移動賦值),與引數交換,然後讓解構函式清理它。

// copy assignment (copy-and-swap idiom)
T& T::operator=(T other) noexcept // call copy or move constructor to construct other
{
    std::swap(size, other.size); // exchange resources between *this and other
    std::swap(mArray, other.mArray);
    return *this;
} // destructor of other is called to release the resources formerly managed by *this

此形式自動提供強異常保證,但禁止資源重用。

[編輯] 流提取和插入

接受 std::istream&std::ostream& 作為左運算元的 operator>>operator<< 的過載稱為插入和提取運算子。由於它們將使用者定義型別作為右運算元(a @ b 中的 b),因此它們必須作為非成員函式實現。

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() 時,它成為一個函式物件型別。

這種型別的物件可以在函式呼叫表示式中使用

// An object of this type represents a linear function of one variable a * x + b.
struct Linear
{
    double a, b;
 
    double operator()(double x) const
    {
        return a * x + b;
    }
};
 
int main()
{
    Linear f{2, 1};  // Represents function 2x + 1.
    Linear g{-1, 0}; // Represents function -x.
    // f and g are objects that can be used like a function.
 
    double f_0 = f(0);
    double f_1 = f(1);
 
    double g_0 = g(0);
}

許多標準庫演算法接受函式物件以自定義行為。 operator() 沒有特別值得注意的規範形式,但為了說明用法

#include <algorithm>
#include <iostream>
#include <vector>
 
struct Sum
{
    int sum = 0;
    void operator()(int n) { sum += n; }
};
 
int main()
{
    std::vector<int> v = {1, 2, 3, 4, 5};
    Sum s = std::for_each(v.begin(), v.end(), Sum());
    std::cout << "The sum is " << s.sum << '\n';
}

輸出

The sum is 15

[編輯] 自增和自減

當字尾自增或自減運算子出現在表示式中時,相應的使用者定義函式(operator++operator--)將以整數引數 0 呼叫。通常,它被宣告為 T operator++(int)T operator--(int),其中引數被忽略。字尾自增和自減運算子通常透過字首版本實現

struct X
{
    // prefix increment
    X& operator++()
    {
        // actual increment takes place here
        return *this; // return new value by reference
    }
 
    // postfix increment
    X operator++(int)
    {
        X old = *this; // copy old value
        operator++();  // prefix increment
        return old;    // return old value
    }
 
    // prefix decrement
    X& operator--()
    {
        // actual decrement takes place here
        return *this; // return new value by reference
    }
 
    // postfix decrement
    X operator--(int)
    {
        X old = *this; // copy old value
        operator--();  // prefix decrement
        return old;    // return old value
    }
};

儘管字首自增和自減運算子的規範實現透過引用返回,但與任何運算子過載一樣,返回型別是使用者定義的;例如,std::atomic 的這些運算子的過載透過值返回。

[編輯] 二元算術運算子

二元運算子通常實現為非成員函式以保持對稱性(例如,當新增一個複數和一個整數時,如果 operator+ 是複數型別的成員函式,那麼只有 complex + integer 可以編譯,而 integer + complex 則不能)。因為每個二元算術運算子都存在一個相應的複合賦值運算子,二元運算子的規範形式是透過其複合賦值來實現的

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<,並要求它實現嚴格弱序(從而滿足比較要求)。為結構實現嚴格弱序的慣用方法是使用 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); }

當提供了三路比較(例如 std::memcmpstd::string::compare)時,所有六個雙向比較運算子都可以透過它來表達

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 和非 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]; }
};

或者,它們可以表示為使用顯式物件引數的單個成員函式模板

struct T
{
    decltype(auto) operator[](this auto& self, std::size_t idx) 
    { 
        return self.mVector[idx]; 
    }
};
(C++23 起)

如果已知值型別為標量型別,則 const 變體應按值返回。

如果不需要或不可能直接訪問容器的元素,或者需要區分左值 c[i] = v; 和右值 v = c[i]; 的用法,operator[] 可以返回一個代理。例如參見 std::bitset::operator[]

operator[] 只能帶一個下標。為了提供多維陣列訪問語義,例如實現 3D 陣列訪問 a[i][j][k] = x;operator[] 必須返回一個 2D 平面的引用,該平面必須有自己的 operator[],它返回一個 1D 行的引用,該行必須有 operator[],它返回元素的引用。為了避免這種複雜性,一些庫選擇過載 operator(),以便 3D 訪問表示式具有類似 Fortran 的語法 a(i, j, k) = x;

(直至 C++23)

operator[] 可以接受任意數量的下標。例如,一個 3D 陣列類的 operator[],宣告為 T& operator[](std::size_t x, std::size_t y, std::size_t z);,可以直接訪問元素。

#include <array>
#include <cassert>
#include <iostream>
 
template<typename T, std::size_t Z, std::size_t Y, std::size_t X>
struct Array3d
{
    std::array<T, X * Y * Z> m{};
 
    constexpr T& operator[](std::size_t z, std::size_t y, std::size_t x) // C++23
    {
        assert(x < X and y < Y and z < Z);
        return m[z * Y * X + y * X + x];
    }
};
 
int main()
{
    Array3d<int, 4, 3, 2> v;
    v[3, 2, 1] = 42;
    std::cout << "v[3, 2, 1] = " << v[3, 2, 1] << '\n';
}

輸出

v[3, 2, 1] = 42
(C++23 起)

[編輯] 位算術運算子

實現位掩碼型別要求的使用者定義類和列舉需要過載位算術運算子 operator&operator|operator^operator~operator&=operator|=operator^=,並且可以選擇過載移位運算子 operator<< operator>>operator>>=operator<<=。規範實現通常遵循上述二元算術運算子的模式。

[編輯] 布林非運算子

運算子 operator! 通常由旨在用於布林上下文的使用者定義類過載。此類類還提供一個使用者定義的轉換為布林型別的函式(參見 std::basic_ios 的標準庫示例),operator! 的預期行為是返回與 operator bool 相反的值。

(C++11 前)

由於內建運算子 ! 執行bool 的上下文轉換,旨在用於布林上下文的使用者定義類可以只提供 operator bool 而無需過載 operator!

(C++11 起)

[編輯] 不常過載的運算子

以下運算子很少過載

  • 取址運算子,operator&。如果一元 & 應用於不完整型別的左值,並且完整型別聲明瞭過載的 operator&,則未指定該運算子是具有內建含義還是呼叫運算子函式。因為此運算子可以過載,通用庫使用 std::addressof 獲取使用者定義型別物件的地址。規範過載 operator& 最著名的例子是 Microsoft 類 CComPtrBase。該運算子在 EDSL 中的使用示例可以在 boost.spirit 中找到。
  • 布林邏輯運算子 operator&&operator||。與內建版本不同,過載無法實現短路求值。也與內建版本不同,它們不會在右運算元之前對左運算元進行排序。(C++17 前) 在標準庫中,這些運算子僅為 std::valarray 過載。
  • 逗號運算子,operator,與內建版本不同,過載不會在右運算元之前對其左運算元進行排序。(C++17 前) 由於此運算子可以過載,通用庫使用諸如 a, void(), b 而非 a, b 來對使用者定義型別的表示式執行排序。Boost 庫在 boost.assignboost.spirit 和其他庫中使用了 operator,。資料庫訪問庫 SOCI 也過載了 operator,
  • 透過成員指標進行成員訪問 operator->*。過載此運算子沒有特定的缺點,但在實踐中很少使用。有人建議它可以作為智慧指標介面的一部分,實際上,boost.phoenix 中的 actor 就以這種方式使用它。在 EDSLs(例如 cpp.react)中更常見。

[編輯] 注意

特性測試 標準 特性
__cpp_static_call_operator 202207L (C++23) static operator()
__cpp_multidimensional_subscript 202211L (C++23) static operator[]

[編輯] 關鍵詞

operator

[編輯] 示例

#include <iostream>
 
class Fraction
{
    // or C++17's std::gcd
    constexpr int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
 
    int n, d;
public:
    constexpr Fraction(int n, int d = 1) : n(n / gcd(n, d)), d(d / gcd(n, d)) {}
 
    constexpr int num() const { return n; }
    constexpr int den() const { return d; }
 
    constexpr 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();
}
 
constexpr bool operator==(const Fraction& lhs, const Fraction& rhs)
{
    return lhs.num() == rhs.num() && lhs.den() == rhs.den();
}
 
constexpr bool operator!=(const Fraction& lhs, const Fraction& rhs)
{
    return !(lhs == rhs);
}
 
constexpr Fraction operator*(Fraction lhs, const Fraction& rhs)
{
    return lhs *= rhs;
}
 
int main()
{
    constexpr 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';
    static_assert(f3 == f2 * 10);
}

輸出

3/8 * 1/2 = 3/16
1/2 * 5/1 = 5/2
2 * 3/8 = 3/4

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 1481 C++98 非成員字首自增運算子只能有一個引數
的類型別、列舉型別或對此類型別的引用型別
無型別要求
CWG 2931 C++23 顯式物件成員運算子函式只能沒有引數
的類型別、列舉型別或對此類型別的引用型別
已禁止

[編輯] 另請參見

常見運算子
賦值 遞增
遞減
算術 邏輯 比較 成員
訪問
其他

a = b
a += b
a -= b
a *= b
a /= b
a %= b
a &= b
a |= b
a ^= b
a <<= b
a >>= b

++a
--a
a++
a--

+a
-a
a + b
a - b
a * b
a / b
a % b
~a
a & b
a | b
a ^ b
a << b
a >> b

!a
a && b
a || b

a == b
a != b
a < b
a > b
a <= b
a >= b
a <=> b

a[...]
*a
&a
a->b
a.b
a->*b
a.*b

函式呼叫

a(...)
逗號

a, b
條件運算子

a ? b : c
特殊運算子

static_cast 將一種型別轉換為另一種相關型別
dynamic_cast 在繼承層次結構內進行轉換
const_cast 新增或移除 cv-限定符
reinterpret_cast 將型別轉換為不相關型別
C 風格轉換 透過 static_castconst_castreinterpret_cast 的混合將一種型別轉換為另一種型別
new 建立具有動態儲存期的物件
delete 銷燬先前由 new 表示式建立的物件並釋放獲得的記憶體區域
sizeof 查詢型別的大小
sizeof... 查詢 的大小 (C++11 起)
typeid 查詢型別的型別資訊
noexcept 檢查表示式是否可以丟擲異常 (C++11 起)
alignof 查詢型別的對齊要求 (C++11 起)

[編輯] 外部連結

  1. StackOverflow C++ FAQ 上的運算子過載