C++基本功之Operator

來源:互聯網
上載者:User

標籤:調整   調用   變數   c++   判斷   驗證   轉換   xxx   result   

廢話不多說,這次講的是 Operator overload. 關於operator, 在 < The C++ Programing Language > 裡的描述,可以用做overload的如下:

+ * / % ^ & | ~ ! = < > += = *= /= %= ^= &= |= << >> >>= <<= == != <=
>= && || ++ >* , > [] () new new[] delete delete[]

先從 operator = 說起。上章中Aear已經提到過,這裡再次強調下,只要是在class member中有 pointer的存在,class 就應該提供 copy constructor 和 operator =.

除了 operator =以外,最常用的operator就是 + - * / += -+ *= /= ++ --。 由於四則運算大同小異,我們就拿簡單的 + 來舉例子說明。讓我們看下下面的這段代碼有什麼問題:

class Test {
private:
    int internalData;
public:
    // constructor and destructor
    Test(int data = 0) : internalData(data) {};
    Test(const Test & Para) : internalData(Para.internalData) {};
    ~Test() {};
  
    // Operator overlording
    Test & operator += (const Test & Para1);
    Test operator + (const Test & Para1); 
};

Test & Test::operator += ( const Test & Para1 )
{
    internalData += Para1.internalData;
    return * this;
}

Test Test::operator + (const Test & Para1)
{
    Test result;
    result.internalData = internalData + Para1.internalData;
    return result;
}

首先讓我們比較一下 += 和 + 兩個operator的效率,我們可以看到,在"+="中,返回的是 *this的reference,而在+中,返回的是一個臨時建立的 result,並且 result object在返回的過程中,由於是return by value,因此compiler會掉用copy constructor把result拷貝到傳回值中,然後掉用 result.~test()銷毀掉這個臨時變數。也就是說,使用 + 比 += 要產生更多的overhead。在某些效率第一的情況下,臨時變數的constructor和destructor都是非常昂貴的操作。比如:

=============one temporary object=============
Test x1 = x2 + x3;

為了提高效率,我們可以這麼寫:

=============no temporary object=============
Test x1(x2);
x1 += x3;     

簡單一點的運算式有可能會被compiler最佳化掉,但是複雜的運算式,只能才用手動的形式最佳化。

讓我們再仔細的看下 Test Test::operator + (const Test & Para1) 這個函數。如果考慮程式的具體實現,我們可以看出來 operator += 和 operator +沒有本質的卻別,但是如果我們需要改動 Test + 的操作,就必須同時更改 operator += 和 operator + 實現,對同一種功能在幾處不同的地方進行維護,並不是良好的設計,因此,我們應該對 Test Test::operator + (const Test & Para1) 做如下改動,使得程式更加容易維護:

=============easy to maintain=============
Test Test::operator + (const Test & Para1)
{
    Test result(*this);
    result += Para1;
    return result;
}

可以看到在operator +裡調用了 +=的操作,因此,如果我們需要給加法賦予不同的含義,只需要更改 operator += 就足夠了。

讓我們繼續深入的看 Test Test::operator + (const Test & Para1)。值得強調的是,無論如何temporary object是必須存在的,無數大師的經驗證明,想用static member, dynamic memory等方法消除掉 temporary object 的 construction 和 destruction,都會產生這樣或者那樣的邏輯和程式錯誤。

但是我們仍然可以利用compiler來對這個temporary object進行最佳化。對於Test Test::operator + (const Test & Para1) 這樣的操作,compiler會傳遞給 operator + 一個 temporary object, 如果 operator +裡的代碼合適,那麼compiler就會用這個temporary object 代替程式裡建立的temporary object,從而減少了constructor和destructor的掉用。經過compiler最佳化後的pseudocode如下:

Test::operator + (Test & Temporary, const Test & Para1)
{
    Temporary.internalData = internalData + Para1.internalData;
    return;
}

這樣減少了temporary object 的construction 和 destruction,效率就會提高很多,但是要想使compiler能夠進行 return by value的最佳化,必須滿足2個條件:

1. class 必須有 copy constructor
2. 在代碼中明確表示,返回的是個temporary object.

因此,出了提供copy constructor外,還必須對 operator + 進行適當的修改,最終代碼如下:

==========Final Version for Maintenance and Optimization==========

Test Test::operator + (const Test & Para1)
{
    return Test(*this) += Para1;
}

在這個代碼裡邊,最明顯的區別就是沒有result這個變數,而是返回一個臨時建立的Test object,因此compiler就會知道這個函數可以用臨時變數最佳化。

也許你會感到驚訝,不過這個operator +還不是最快速的。因為我們看到,在Test(*this) += Para1 的過程中,調用了一次constructor 一次 operator +=,但是已經足夠了。不過為了效率,我們有更加極端的方法,在如下的代碼中,我們捨棄了可讀性,可維護性等等,只為了更快的速度:

==========糟糕的風格,不過更快==========
class Test {
    ...   
private:
    // cosntructor for operator +
    Test(int data, const Test & Para1 ) : internalData(data + Para1.internalData) {};
};

Test Test::operator + (const Test & Para1)
{
    return Test(internalData, Para1);
}

如果同時還要定義 operator - * /等操作的constructor,我們可以適當更改constructor的signature,從而可以用constructor實現不同的運算

關於operator第一部分今天就說這麼多,大家有空去坐坐 http://blog.sina.com.cn/u/1261532101 ;下次見。

 

======================================================
 大家請把我的文章當參考,詳細內容  還請參照 權威書籍 
 <c++ programming language>如果文中有錯誤和遺漏,
 請指出,Aear會儘力更正, 謝謝!
======================================================

 

繼續上一章的內容,下面是經過調整後的Test Class代碼:

class Test {
private:
    int internalData;
public:
    // constructor and destructor
    Test(int data = 0) : internalData(data) {};
    Test(const Test & Para) : internalData(Para.internalData) {};
    ~Test() {};
  
    // Operator overlording
    Test & operator += (const Test & Para1);
    Test operator + (const Test & Para1); 
};

Test & Test::operator += ( const Test & Para1 )
{
    internalData += Para1.internalData;
    return * this;
}

Test Test::operator + (const Test & Para1)
{
    return Test(*this) += Para1;
}
下面我們假設要給這個Test Class添加一種新的功能,讓Test Class 和 Integer之間能夠進行加法操作。 也就是說可以執行下列代碼:

Test x1(10);
x1 = x1 + 5;
x1 += 5;

實際上,我們不需要進行任何修改,上面的代碼就能夠正確執行。因為我們提供的建構函式Test(int data = 0) 能夠隱性的 (implicit type conversion) 把一個integer 轉換成一個Temporary Test Object,然後掉用 Test Test::operator + (const Test & Para1)。因此,上面的代碼等同於:

x1 = x1.operator + (Test(5));
x1 = x1.operator += (Test(5));

Implicit Type Conversion 實際上會帶來很多的麻煩,要想避免潛在的危險,最好在Test(int data = 0)前面加上explicit,表示如果對interger轉換成Test,必須由程式員來控制,compiler不得進行隱性的操作。因此,要想似的 x1 = x1 + 5能夠正常運行,有2種方法:

x1 = x1 + static_cast<Test>(5);

x1 = x1 + Test(5);

還有一點需要注意的是,如果不用explicit type conversion,可以運行:

x1 = x1 + 5;

但是在編譯:

x1 = 5 + x1

的時候就會報錯了,除非使用一個Temporary Object ,如:

x1 = Test(5) + x1;

要想使Test Class 支援 x1 = 5 + x1,最好的方法就是用helper function. 下面我們來看看Operator的另外一中定義方式。

==================分割線==================

我們可以使用friend function 來定義Test Class 的加法運算,代碼如下:

class Test {
    Test(int data = 0) : internalData(data) {};
    ...
    // 針對這個Test Class, 並不需要下面這行。
    friend Test operator + ( const Test & Para1, const Test & Para2);
};

Test operator + ( const Test & Para1, const Test & Para2)
{
    return Test(Para1) += Para2;
}

首先我們需要注意的是,Test(int data = 0)沒有用explicit,也就是說可以進行隱性的類型轉換,因此無論是運行:
    x1 = x1 + 5;
還是:
    x1 = 5 + x1;
都能夠編譯通過。

解決了基本的功能問題,讓我們繼續考慮一下效率。無論是在x1 = x1 + 5,還是在x1 = 5 + x1,都至少會掉用額外的constructor和destructor把5轉換成Test Object,這種浪費是很沒有必要的。其次允許compiler進行implicit type conversion並不是一個良好的習慣。解決這些問題的方法,就是提供專用的 operator + 來進行integer和Test object之間的加法操作,具體代碼如下:

========== 支援 x1 + 5 ==========
Test operator + ( const Test & Para1, int Para2)
{
    return Test(Para2) += Para1;
}

========== 支援 5 + x1 ==========
Test operator + ( int Para1, const Test & Para2 )
{
    return Test(Para1) += Para2;
}

同時還要在class Test中加如下面2行(對於此例子並不需要,不過正常情況是需要的):

friend Test operator + ( int Para1, const Test & Para1 );
friend Test operator + ( const Test & Para1, int Para2 );

並且在constructor前面加上 explicit。當然,你也可以用Template進行定義,如下:

========== 支援 x1 + 5 ==========
template <class T>
T operator + ( const T & Para1, int Para2)
{
    return T(Para2) += Para1;
}

實際上對於 template的定義,我個人並不推薦. 首先是因為namespace的問題,到底是global namespace呢?還是一個local namespace?如果是global namespace,那不一定所有的global class 都需要 operator +,這樣就提供了多餘的class操作。local namespace倒是可以用,前提是所有的class都定義了 +=. 也許對於大多數class來講,並不需要operator + 的操作。所以我覺得對於 operator 的定義,盡量少用 template (個人觀點).

==================分割線==================

下面說說關於類型轉換的operator. 對於一個Abstract Data Type來說,類型轉換是經常用到的,比如我們前面提到的從 integer轉換成 Test, 可以使用implicit type conversion 和 explicit type conversion. 但是如果我們想從Test 轉換成 integer,compiler無法支援自動的類型轉換,因此需要我們提供相應的operator:

class Test {
    ...
    // Type converstion from Test to int
    operator int() { return internalData; };
}

那麼我們就可以執行:
    int i = Test(10);

實際上,operator int()又是一種implicit type conversion,這並是收程式員的控制。良好的程式設計,是programmer能夠精確的控制每一個細微的操作。因此並不推薦使用 operator int(),好的方法是按照 < effective c++ > 中給出的那樣,提供一個asInt() method,來做explicti type conversion:

============ explicti ============
class Test {
    ...
    // Type converstion from Test to int
    int asInt() { return internalData; };
}

================== Test++ & ++Test ==================

相信大家都知道 Prefix ++ 和 Postfix ++的區別是什麼,下面是代碼:

// Prefix
Test& operator++()
{
    ++internalData;
    return (*this);
}

// Postfix
Test operator++(int)
{
    ++*this;
    return --Test(*this); // 為了使用傳回值最佳化,需要定義 --Test
}

我們只是簡單的看下效率問題,在 Prefix中也就是 ++ 返回的是reference,沒有temporary object,在 Postfix中返回的是個object,使用了Temporary。相信大家都知道了,能不使用 Test++的地方就不要使用,盡量使用 ++Test。

比如:

for( iterator i = XXX; XXX; ++i) // 不要使用 i++

對於postfix, compiler並不能保證肯定會最佳化成 prefix,所以寫代碼的時候盡量注意。

================== 其他關於Operator ==================

有些operator,並不推薦進行overload,因為會出現無法預料的情況。這些operator 包括:

&&, || , & , |  , ","

舉個簡單的例子,如果你overload了",",那麼有一個for迴圈如下:

for( Test x1 = x2,i = 0; ; ) {....}

到底是x1 = x2 和 i = 0呢?還是 x1 = x2.operator , (i) = 0 呢?如果overload了 & ,對於邏輯判斷,x1 && x2,到底是  x1 && x2呢?還是 x1.operator & (&x2)呢?因此這些overload都會產生很多讓人費解的問題。

其次,很多operator overload需要很小心的對待,這些operator 如下:

new new[] delete delete[] -> [] ()

請仔細閱讀 C++ 標準,瞭解詳細內容後,再對這些operator進行overload,不然很容易造成程式的不穩定。

C++基本功之Operator

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.