c++ overloading用法 運算符重載的基本規則和習慣用法是什麼?




overloading example c++ (6)

注意:答案是按照特定的順序給出的,但是由於許多用戶根據投票分類答案,而不是按照給定的時間排列答案,因此下面是答案索引,按其最有意義的順序排列:

(注意:這是一個Stack Overflow的C ++常見問題解答的入口,如果你想批評在這個表單中提供常見問題的想法,那麼開始所有這些的meta上的貼子應該是這樣做的地方。該問題在C ++聊天室中進行監控,常見問題解決方案首先出現在C ++聊天室中,因此您的答案很可能會被提出這一想法的人閱讀。)


重載newdelete

注意:這只涉及重載newdelete語法 ,而不涉及這樣的重載操作符的實現 我認為重載newdelete的語義應該得到他們自己的常見問題解答 ,在運算符重載的話題中,我永遠不能做正義。

基本

在C ++中,當你編寫一個像new T(arg)這樣的新表達式時,在計算這個表達式時會發生兩件事情:第一個operator new被調用來獲取原始內存,然後T的相應構造函數被調用來將這個原始內存變成有效的對象。 同樣,當你刪除一個對象時,首先調用它的析構函數,然後內存返回給operator delete
C ++允許您調整這兩種操作:內存管理和在分配的內存中構建/銷毀對象。 後者是通過編寫一個類的構造函數和析構函數完成的。 微調內存管理是通過編寫你自己的operator newoperator delete

操作符重載的第一個基本規則 - 不要這樣做 - 特別適用於重載newdelete 。 幾乎導致這些操作符過載的唯一原因是性能問題內存限制 ,並且在許多情況下,其他操作(如對所用算法的更改)將提供比試圖調整內存管理更高的成本/增益比

C ++標準庫附帶一組預定義的new操作符和delete操作符。 最重要的是這些:

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(); 

前兩個分配/釋放對象的內存,後兩個分配對像數組。 如果你提供你自己的版本,它們不會超載,但是替換標準庫中的版本。
如果你重載operator new ,你應該總是重載匹配的operator delete ,即使你永遠不打算調用它。 原因是,如果構造函數在評估新表達式時拋出,則運行時系統會將內存返回給operator delete ,該operator delete與調用分配內存以創建對象的operator new相匹配。如果執行了不提供匹配的operator delete ,默認的調用,這幾乎總是錯誤的。
如果你重載newdelete ,你應該考慮重載數組變體。

安置new

C ++允許新的和刪除操作符採取額外的參數。
所謂的placement new允許您在傳遞給某個地址的某個地址創建一個對象:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

標準庫為此提供了新的和刪除操作符的適當重載:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

請注意,在上面給出的用於放置new的示例代碼中,除非X的構造函數拋出異常,否則不會調用operator delete

您也可以使用其他參數重載newdelete 。 與放置new的附加參數一樣,這些參數也在關鍵字new後的括號內列出。 僅僅因為歷史原因,這樣的變體通常也被稱為放置新的,即使他們的論點不是用於將對象放置在特定地址。

特定於類的新建和刪除

Most commonly you will want to fine-tune memory management because measurement has shown that instances of a specific class, or of a group of related classes, are created and destroyed often and that the default memory management of the run-time system, tuned for general performance, deals inefficiently in this specific case. To improve this, you can overload new and delete for a specific class:

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

Overloaded thus, new and delete behave like static member functions. For objects of my_class , the std::size_t argument will always be sizeof(my_class) . However, these operators are also called for dynamically allocated objects of derived classes , in which case it might be greater than that.

Global new and delete

To overload the global new and delete, simply replace the pre-defined operators of the standard library with our own. However, this rarely ever needs to be done.


C ++中運算符重載的三個基本規則

當涉及到C ++中的運算符重載時, 應遵循三條基本規則 。 與所有這些規則一樣,確實有例外。 有時人們偏離了他們,結果並不是錯誤的代碼,但是這種積極的偏差卻很少。 至少,我看到的100個這樣的偏差中有99個是不合理的。 但是,它也可能是1000個中的999個。所以你最好遵守以下規則。

  1. 每當操作員的含義不明確且無可爭議時,不應超載。 相反,提供一個精心挑選名稱的功能。
    基本上,超載運營商的首要原則就是: 不要這樣做 。 這看起來很奇怪,因為有很多關於操作符重載的知識,所以很多文章,書籍章節和其他文章都涉及這些。 但儘管有這些看似明顯的證據, 但只有極少數情況下運營商超載是適當的 。 原因在於實際上很難理解運算符應用背後的語義,除非在應用程序領域中使用運算符是眾所周知且無可爭議的。 與普遍的看法相反,這種情況幾乎不存在。

  2. 始終堅持運營商眾所周知的語義。
    C ++對重載運算符的語義沒有限制。 您的編譯器會高興地接受實現了二元運算符的代碼,從其右操作數中減去。 但是,這樣的操作員的用戶絕不會懷疑表達式a + bb減去b 。 當然,這假設應用程序域中操作符的語義是無可爭議的。

  3. 始終提供一整套相關的操作。
    運營商彼此之間以及與其他運營相關。 如果你的類型支持a + b ,用戶也期望能夠調用a += b 。 如果它支持前綴增量++a ,他們也會期望a++能工作。 如果他們可以檢查a < b ,他們肯定會期望也能夠檢查a > b 。 如果他們可以復制構建您的類型,他們希望分配工作。

繼續進行會員與非會員之間的決定


轉換運營商(也稱為用戶定義的轉化)

在C ++中,您可以創建轉換運算符,這些運算符允許編譯器在類型和其他定義的類型之間進行轉換。 有兩種類型的轉換運算符,即隱式和顯式運算符。

隱式轉換運算符(C ++ 98 / C ++ 03和C ++ 11)

隱式轉換運算符允許編譯器將用戶定義類型的值隱式轉換(如intlong之間的轉換)為某種其他類型。

以下是一個帶有隱式轉換運算符的簡單類:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

隱式轉換運算符(如單參數構造函數)是用戶定義的轉換。 嘗試將調用與重載函數進行匹配時,編譯器將授予一個用戶定義的轉換。

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

起初這看起來非常有幫助,但問題在於,隱式轉換甚至在不期望的時候開始執行。 在下面的代碼中, void f(const char*)將被調用,因為my_string()不是lvalue ,所以第一個不匹配:

void f(my_string&);
void f(const char*);

f(my_string());

初學者很容易出錯,甚至有經驗的C ++程序員有時會感到驚訝,因為編譯器會挑選他們沒有懷疑的超載。 這些問題可以通過顯式轉換運算符來緩解。

顯式轉換運算符(C ++ 11)

與隱式轉換運算符不同,顯式轉換運算符在您不指望它們時不會啟動。 以下是一個帶有顯式轉換運算符的簡單類:

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

注意explicit 。 現在,當您嘗試執行來自隱式轉換運算符的意外代碼時,會出現編譯器錯誤:

prog.cpp: In function ‘int main()’:
prog.cpp:15:18: error: no matching function for call to ‘f(my_string)’
prog.cpp:15:18: note: candidates are:
prog.cpp:11:10: note: void f(my_string&)
prog.cpp:11:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘my_string&’
prog.cpp:12:10: note: void f(const char*)
prog.cpp:12:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘const char*’

要調用顯式的轉換運算符,必須使用static_cast ,C風格轉換或構造函數風格cast(即T(value) )。

但是,有一個例外:編譯器允許隱式轉換為bool 。 此外,編譯器在轉換為bool (允許編譯器一次執行2個隱式轉換,但最多只有1個用戶定義的轉換)後不允許執行另一個隱式轉換。

由於編譯器不會投射“過去” bool ,顯式轉換運算符現在不需要安全布爾成語 。 例如,C ++ 11之前的智能指針使用安全Bool慣用法來防止轉換為整型。 在C ++ 11中,智能指針使用顯式運算符,因為編譯器在將類型顯式轉換為布爾型之後,不允許隱式轉換為整型。

繼續重載newdelete


Why can't operator<< function for streaming objects to std::cout or to a file be a member function?

Let's say you have:

struct Foo
{
   int a;
   double b;

   std::ostream& operator<<(std::ostream& out) const
   {
      return out << a << " " << b;
   }
};

Given that, you cannot use:

Foo f = {10, 20.0};
std::cout << f;

Since operator<< is overloaded as a member function of Foo , the LHS of the operator must be a Foo object. Which means, you will be required to use:

Foo f = {10, 20.0};
f << std::cout

which is very non-intuitive.

If you define it as a non-member function,

struct Foo
{
   int a;
   double b;
};

std::ostream& operator<<(std::ostream& out, Foo const& f)
{
   return out << f.a << " " << f.b;
}

You will be able to use:

Foo f = {10, 20.0};
std::cout << f;

這非常直觀。


C ++中運算符重載的一般語法

您不能在C ++中為內置類型更改運算符的含義,只能為用戶定義的類型重載運算符1 。 也就是說,至少有一個操作數必須是用戶定義的類型。 與其他重載函數一樣,運算符只能為一組參數重載一次。

並不是所有的操作符都可以用C ++重載。 無法超載的運營商包括: . :: sizeof typeid .*和C ++中唯一的三元運算符, ?:

在C ++中可以重載的操作符包括:

  • 算術運算符: + - * / %+= -= *= /= %= (所有二進制中綴); + - (一元前綴); ++ -- (一元前綴和後綴)
  • 位操作: & | ^ << >>&= |= ^= <<= >>= (所有二進制中綴); ~ (一元前綴)
  • 布爾代數: == != < > || >= || && (全部二進制中綴); ! (一元前綴)
  • 內存管理: new new[] delete delete[]
  • 隱式轉換運算符
  • miscellany: = [] -> ->* , (all binary infix); * & (所有一元前綴) () (函數調用,n元中綴)

然而,你可以重載所有這些的事實並不意味著你應該這樣做。 請參閱運算符重載的基本規則。

在C ++中,運算符以具有特殊名稱函數的形式被重載。 與其他函數一樣,重載運算符通常可以作為其左操作數類型 的成員函數或作為非成員函數來實現 。 無論你是自由選擇還是使用其中一種,都取決於幾個標準。 2應用於對象x的一元運算符@ 3被調用為[email protected](x)[email protected]() 。 應用於對象xy的二進制中綴運算符@被稱為[email protected](x,y)[email protected](y)4

作為非成員函數實現的操作符有時是操作數類型的朋友。

1 術語“用戶定義”可能會有些誤導。 C ++區分了內置類型和用戶定義類型。 前者屬於例如int,char和double; 到後者屬於所有結構,類,聯合和枚舉類型,包括來自標準庫的類型,即使它們不是由用戶定義的類型。

2 本FAQ 的後面部分將對此進行介紹。

3 @不是C ++中的有效運算符,因此我將它用作佔位符。

4 C ++中唯一的三元運算符不能被重載,並且唯一的n元運算符必須始終作為成員函數來實現。

繼續以C ++運算符重載的三個基本規則


會員與非會員之間的決定

二元運算符= (賦值), [] (數組預訂), -> (成員訪問)以及n-ary () (函數調用)運算符必須始終作為成員函數實現,因為語言要求他們去。

其他運營商可以作為成員或非成員實施。 但是,其中一些通常必須作為非成員函數來實現,因為它們的左操作數不能被您修改。 其中最突出的是輸入和輸出操作符<<>> ,其左操作數是來自標準庫的流類,您不能更改它。

對於您必須選擇將它們實現為成員函數或非成員函數的所有運算符,請使用以下經驗法則來決定:

  1. 如果它是一個一元運算符 ,則將其作為成員函數來實現。
  2. 如果一個二元運算符平等地對待這兩個操作數 (它使它們保持不變),則將此運算符實現為非成員函數。
  3. 如果一個二元運算符沒有同時處理它的兩個操作數(通常它會改變它的左操作數),如果它必須訪問操作數的私有部分,使它成為左操作數類型的成員函數可能會很有用。

當然,正如所有的經驗法則一樣,也有例外。 如果你有一個類型

enum Month {Jan, Feb, ..., Nov, Dec}

並且你想為它重載遞增和遞減運算符,你不能將它作為成員函數,因為在C ++中,枚舉類型不能有成員函數。 所以你必須把它作為一個自由函數來重載。 嵌套在類模板中的類模板的operator<()在類定義中作為內聯成員函數完成時更容易編寫和讀取。 但這些確實是罕見的例外。

(但是, 如果你做了一個例外,不要忘記操作數的const -ness問題,對於成員函數,它成為隱含的this參數。如果操作符作為非成員函數將其最左邊的參數作為一個const引用,與成員函數相同的運算符需要在最後有一個const ,以使*this是一個const引用。)

繼續常用運算符超載







c++-faq