C++ articles:Guru of the Week #4 -- Class Mechantics (轉)
作者:Hub Sutter
譯者:plpliuly
/*此文是譯者出於自娛翻譯的GotW(Guru of the Week)系列文章第3篇,原文的版權是屬於Hub Sutter(著名的C++專家,"Exceptional C++"的作者)。此文的翻譯沒有徵得原作者的同意,只供學習討論。——譯者
*/
#4 類的結構(Class Mechanics)
難度:7.5/10
你對定義一個類牽涉到的具體細節熟悉多少?這次GotW不僅討論一些寫一個類時很容易犯的錯誤,也要討論怎樣使你寫的類具有專業風格.
問題:
假設你在看一段程式碼.其中包含如下一個類定義.這個類定義中有幾處風格差勁,還有幾處是的的確確的錯誤.你可以找到幾處,該怎樣修改?
class Complex {
public:
Complex( double real, double imaginary = 0 )
: _real(real), _imaginary(imaginary) {};
void operator+ ( Complex other ) {
_real = _real + other._real;
_imaginary = _imaginary + other._imaginary;
}
void operator< os << "(" << _real << "," << _imaginary << ")";
}
Complex operator++() {
++_real;
return *this;
}
Complex operator++( int ) {
Complex temp = *this;
++_real;
return temp;
}
private:
double _real, _imaginary;
};
答案:
序
這個類定義中出現的需要修改或改進的地方遠不止我們在下面要提到的幾處.我們提出這個問題的目的主要是為了討論類定義的構成(比如,"<
0.既然標準庫中已經有複數類為什麼還要自己定義一個Complex類呢?(而且,標準庫中定義的類不會有下面提及的任何一個問題,是由業界中具有多年的最優秀的人編寫.還是謙虛一點去直接重用這些類吧)
[忠告]儘量直接使用用標準庫演算法.那會比你自己動手寫一個要快捷,而且不易出錯.
class Complex {
public:
Complex( double real, double imaginary = 0 )
: _real(real), _imaginary(imaginary) {};
1.風格問題:這個建構函式可以當作單引數函式使用,這樣就可能作為一個隱式轉換函式使用.但是可能並不是所有時候發生的隱式轉換都是你所期望的.
[忠告]當心不易察覺的型別轉換操作在你並不希望的時候發生.一個好的避免辦法就是:可能的話就用explicit修飾詞限制建構函式.
void operator+ ( Complex other ) {
_real = _real + other._real;
_imaginary = _imaginary + other._imaginary;
}
2.風格問題:從上講,引數應該是const&型別,而且"a=a+b"形式的語句應該換成"a+=b"的語句.
[準則]儘量使用const&引數型別代替複製傳值型別引數.
[忠告]對於算術運算,儘量使用"a op= b"代替"a = a op b".(注意有些類--比如一個別人寫的類--可能透過運算子的過載改變了op和op=之間的關係)
3.風格問題:+運算子不應該定義為成員函式.如果被定義成如上述程式碼中的成員函式,你就只能用"a=b+1"形式的語句而不能用形如"a=1+b"的語句.這樣,你就需要同時提供operator+(Complex,int)和operator+(int,Complex)的定義.
[準則]在考慮把一個運算子函式定義為成員函式還是非成員函式的時候,儘量參照下面的原則:(Lakos96:143-144;591-595;Murray93:47-49)
- 一元(單引數)運算子函式應該定義成成員函式
- =,(),[],和->運算子應該定義成成員函式
- +=,-=,/=,*=,(等等)應該定義成成員函式
- 其餘的運算子都應該定義成非成員函式
4.錯誤:+運算子不應該改變自身.它應該返回包含相加結果的臨時物件.請注意,為了防止類似"a+b=c"的操作,這個運算子函式返回型別應該是"const Complex"(而不僅僅是"Complex").
(實際上,上述的程式碼更象定義+=運算子函式的程式碼)
5.風格問題:一般來講,當定義了op的運算子函式,就應該同時定義了op=運算子.因此,這兒也應該定義+=運算子.上面的程式碼就應當是+=的定義(但是返回型別需要修改,參看下面的討論).
void operator< os << "(" << _real << "," << _imaginary << ")";
}
(注意:對於一個真正的<
6.錯誤:運算子< 7.錯誤:這個運算子函式應該返回"ostream&",而且應該以"return os"結束.這樣就可以支援將多個輸出操作連結起來(比如"cout << a << b;").
[準則]運算子<>都應該返回stream的引用.
Complex operator++() {
++_real;
return *this;
}
8.風格問題:前自增應該返回Complex&,以便呼叫者可以更直接的操作.(譯者:其實我覺得這不僅僅是風格的問題.因為按照前自增的標準定義,應該支援"++++a"的語法,而且兩次前自增都應該是對a物件的自身操作,如果返回Complex型別,那第二次前自增呼叫的是臨時物件的前自增操作.)
Complex operator++( int ) {
Complex temp = *this;
++_real;
return temp;
}
9.風格問題:後自增應該返回"const Complex".這可以防止形如"a++++"的用法.這句話可不會象某些人想當然那樣會連續對a物件作兩次自增操作.
10.風格問題:如果透過前自增操作來實現後自增運算子函式將會更好.(譯者:將"++_real;"替換為"++(*this);")
[準則]儘量透過前自增操作來實現後自增操作.
private:
double _real, _imaginary;
};
11.風格問題:儘量避免使用以下劃線開頭命名變數.我曾經也很習慣這樣定義變數,就連著名的"Design Patterns"(Gamma et al)中也是這樣.但是因為標準庫的實現中保留了很多下劃線開頭識別符號,如果我們要用下劃線開頭定義自己的變數就得記住全部已經保留的識別符號以免衝突,這太難了.(既然使用下劃線作為成員變數的標誌容易跟保留識別符號衝突,那我就在變數結尾加下劃線)
好了.最後,不考慮其他一些上面沒有提到的設計和風格上的缺陷,我們可以得到下面的經過修正的程式碼:
class Complex {
public:
explicit Complex( double real, double imaginary = 0 )
: real_(real), imaginary_(imaginary) {}
Complex& operator+=( const Complex& other ) {
real_ += other.real_;
imaginary_ += other.imaginary_;
return *this;
}
Complex& operator++() {
++real_;
return *this;
}
const Complex operator++( int ) {
Complex temp = *this;
++(*this);
return temp;
}
ostream& print( ostream& os ) const {
return os << "(" << real_
<< "," << imaginary_ << ")";
}
private:
double real_, imaginary_;
friend ostream&
operator< };
const Complex operator+( const Complex& lhs,
const Complex& rhs ) {
Complex ret( lhs );
ret += rhs;
return ret;
}
ostream& operator< const Complex& c ) {
return c.print(os);
}
-----
(結束)
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988213/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- C++ articles:Guru of the Week #1 (轉)C++
- C++ articles:Guru of the Week #3:使用標準庫 (轉)C++
- Guru of the week:#18 迭代指標. (轉)指標
- Guru of the week:#17 型別對映. (轉)型別
- Guru of the week:#19 自動型別轉換. (轉)型別
- Guru of the Week 條款19:自動轉換 (轉)
- Guru of the Week 條款27:轉呼叫函式 (轉)函式
- Guru of the Week 條款28:“Fast Pimpl”技術 (轉)AST
- Guru of The week #20 程式碼的複雜性 Ⅰ. (轉)
- Guru of the Week 條款09:記憶體管理(上篇) (轉)記憶體
- Guru of the Week 條款10:記憶體管理(下篇) (轉)記憶體
- Guru of the Week 條款24:編譯級防火牆 (轉)編譯防火牆
- Guru of the Week 條款30附錄:介面原則 (轉)
- Guru of the Week 條款05:覆寫虛擬函式 (轉)函式
- Guru of the Week 條款13:物件導向程式設計 (轉)物件程式設計
- Guru of the Week #5:虛擬函式的重新定義 (轉)函式
- Guru of the Week 條款07:編譯期的依賴性 (轉)編譯
- Guru of the Week 條款11:物件等同(Object Identity)問題 (轉)物件ObjectIDE
- Guru of the Week 條款14:類之間的關係(上篇) (轉)
- Guru of the Week 條款15:類之間的關係(下篇) (轉)
- Guru of the Week 條款16:具有最大可複用性的通用Containers (轉)AI
- Guru of the Week 條款08:GotW挑戰篇——異常處理的安全性 (轉)Go
- Guru of the Week 條款23:物件的生存期(第二部分) (轉)物件
- Week 4 Problems
- Guru of the Week 條款22:物件的生存期(第一部分) (轉)物件
- Guru of the Week 條款21:程式碼的複雜性(第二部分) (轉)
- Guru of the Week 條款20:程式碼的複雜性(第一部分) (轉)
- 【Coursera GenAI with LLM】 Week 2 PEFT Class NotesAI
- LeetCode Week 4LeetCode
- OpenGL Matrix Class (C++)C++
- C++ Empty Class OptimizationC++
- 【Coursera GenAI with LLM】 Week 3 Reinforcement Learning from Human Feedback Class NotesAI
- angularJS articles and resourcesAngularJS
- 【week4】課堂Scrum站立會議Scrum
- C++ 11 新特性之ClassC++
- C++基礎:: struct vs classC++Struct
- More Effective C++ 條款4 (轉)C++
- Coursera課程筆記----C++程式設計----Week3筆記C++程式設計