設計與宣告(一)

2021-07-22 07:21:27 字數 4402 閱讀 9692

條款18:讓介面容易被正確使用,不易被誤用

開發乙個「容易被正確使用,不易被誤用」的介面,首先必須考慮使用者會做出什麼樣的錯誤。以下為例:

class date

;

乍見之下這個介面通情達理,但是至少容易犯兩個錯誤。第一,他們可能以錯誤的次序傳遞引數;第二, 他們可能傳遞乙個無效的引數。許多客戶端錯誤可以因為匯入新的型別而獲得預防:

class month}; 

class day};

class year

};class date

};

預防客戶錯誤的另乙個辦法是限制型別內什麼事可做,什麼事不可做,常見的限制是加上

const。如下例:

if(a*b = c) …//其實原意是做一次比較動作

如果過載的乘法操作返回的是const,編譯器就會識別出這種賦值運算不恰當了。

另外,「除非有好理由,否則應該盡量令你的

types

的行為與內建

types

一致」。比如不要在乘法運算中做加法運算。很少有其他性質比「一致性」更能導致介面容易被正確使用。比如長度,不要有的型別用size表示,有的型別用length表示。雖然有些ide外掛程式能夠自動去尋找相應的方法名,但「不一致性對開發人員造成的心理和精神上的摩擦與爭執,沒有任何乙個ide可以完全抹除」。

任何介面如果要求客戶必須記得做某件事,就有著「不正確使用」的傾向,因為客戶可能忘記做某件事,最容易造成資源洩露。在前面資源管理中提到的智慧型指標,讓專門的資源管理來掌握資源的**:

auto_ptrcreateinvestment();
shared_ptrcreateinvestment();
shared_ptr有乙個特別好的性質:它會自動使用它的「每個指標專屬的刪除器」,因而消除另乙個潛在的客戶錯誤:所謂的「cross-dllproblem」。這個問題發生於在不同的dll中出生(new)和刪除(delete)的情況(物件生命週期橫跨兩個dll,但在第二個dll中結束生命的時候卻希望呼叫的是第乙個dll的析構函式),自定義刪除器則會在刪除時仍然呼叫誕生時所在的那個dll的析構函式。智慧型指標比原始指標大且慢,而且使用輔助動態記憶體。在許多應用程式中這些額外的執行成本並不高,然而其降低客戶錯誤的成效確很明顯。

請記住:

1、 好的介面容易被正確使用,不易被誤用

2、 介面的一致性,以及與內建型別的行為相容

3、 「阻止誤用」的辦法包括建立新型別、限制型別上的操作、束縛物件值、以及消除客戶的資源管理責任

4、 多使用資源管理類如智慧型指標shared_ptr、auto_ptr等進行資源管理

條款19:設計class猶如設計type

新type的物件應該如何被建立和銷毀?這影響到class的建構函式、析構函式以及記憶體分配和釋放函式的設計。

物件的初始化和物件的賦值該有什麼樣的差別?決定拷貝建構函式和賦值操作符的行為,以及之間的差異

新type的物件如果被passed by value,意味著什麼?拷貝建構函式的實現

什麼是新的type的「合法值」?對於class的成員變數而言,通常只有某些數值是有效的,所以通常要進行錯誤檢查、異常處理等

新的type需要配合某個繼承的類嗎?繼承某些classes必然受到那些classes的設計的束縛,特別是virtual和non-virtual的影響

新的type需要什麼樣的轉換?如果期待進行允許的型別t1物件隱式轉換為型別t2物件,就必須在classt1寫乙個型別轉換函式(operator t2)或class t2內寫乙個可被單一實參呼叫的建構函式。如果只允許explicit建構函式,就得寫出專門負責指向轉換的函式

什麼樣的操作符和函式對此type而言是合理的?決定class宣告哪些函式

什麼樣的標準函式應該駁回?那些正是你必須宣告為private的

誰該取用新type的成員?決定函式為public、private、protected或friends

什麼是新type的「未宣告介面」它對效率、異常安全性以及資源的運用提供何種保證

新type有多一般化?如果不僅是定義乙個新type,而是一整個types家族,就應該定義新的classtemplate

真的需要乙個新type嗎?

請記住:

class的設計就是type的設計,在定義乙個新的type之前,請確定你已經考慮過本條款中所有討論的主題。

條款20:寧以pass-by-reference-to-const替換pass-by-value

在預設的情況下,c++以by value的方式傳遞物件至函式,這些物件由copy建構函式產出,使得pass-by-value成為昂貴的操作,考慮以下繼承:

classperson;

classstudent:publicperson;

現在有乙個呼叫函式validatestudent,要呼叫乙個student實參並返回它是否有效?

boolvalidatestudent(students);

student plato;

boolplatoisok=validatestudent(plato);

person一次copy建構函式、student一次copy建構函式、四次stringcopy建構函式,以及他們的析構函式。改進方法:

bool validatestudent(const student& s);

這種方式高效:沒有任何建構函式和析構函式,也沒有任何新物件構建。此外,這種方式還可以避免物件的切割(slicing)。

如果窺視c++編譯器的底層,reference往往是以指標的方式實現的,因此對於內建型別passe-by-value更合理,這忠告同樣是適用於stl迭代器和函式物件。

請記住:

1、 盡量以passby reference to const替換pass by value。前者通常比較高效,並可避免切割的問題。

2、 以上規則並不適用於內建型別,以及stl的迭代器和函式物件。對它們而言,passby value更加適當。

條款21:必須返回物件時,別妄想返回其reference

在上條款中談到的pass-by-value一般都以pass-by-reference替代,但是容易存在過度使用的問題:只要看到是乙個非內建型別,就去使用引用傳值。

class rational

friend const rational& operator* (constrational& r1, const rational& r2)

};

函式建立物件有兩種方式:在stack空間或heap空間建立。首先如果定義乙個local變數,就在stack建立物件:

friend constrational& operator* (const rational& r1, const rational& r2)

返回local物件的引用是不可接受的,因為

local

在函式退出前已經被銷毀了。於是讓我們考慮在heap內構造乙個物件,並返回reference指向它:

friend constrational& operator* (const rational& r1, const rational& r2)

無法析構物件,導致資源洩露。

static物件位於全域性靜態區,它的生命週期與這個程式的生命週期是相同的,所以不用擔心它會像棧物件那樣很快消失掉,也不用擔心它會像堆物件那樣有資源洩露的危險。可以像這樣寫:

friend constrational& operator* (const rational& r1, const rational& r2)

這樣可以正常編譯通過,但是對於:

rational a, b, c,d;

if((a*b) == (c*d))

if條件恒為真,這就是靜態物件做的!因為返回值共享這個靜態物件。

乙個必須返回新物件的正確寫法是去掉引用,返回乙個新物件:

friend constrational operator* (const rational& r1, const rational& r2)

當然,你需要承受返回值的構造和析構成本,但是從長原來看是為了獲得正確行為而付出的乙個小小的代價。

請記住:

絕對不要返回pointer或reference指向乙個localstack物件,指向乙個heap-allocated物件也不是好方法,更不能指向乙個local static物件(陣列),需要複製物件的時候,就讓它去複製。

設計與宣告

預設情況下c 是以值傳遞的形式傳遞物件到函式的。除非特別指定,否則函式引數都是以實際實參的副本為初值。呼叫端所獲的也是函式返回值的乙個副本。這些副本是由物件的拷貝建構函式產生,這會使值傳遞非常費時。特別是在乙個結構複雜的類中,例如 class student public person當有以下函式 ...

設計與宣告

條款18 讓介面容易被正確使用,不易被誤用 條款19 設計class猶如設計type 條款20 寧以pass by value to const替換pass by value 預設情況下c 以by value方法給物件傳遞引數,函式引數都是物件的副本,這些副本是由物件的copy建構函式產出,這可能使...

二 設計與宣告

類的設計是一件非常複雜的事情,設計師在設計類的時候不僅僅要考慮實現的功能,又要兼顧擴充套件性 易用性等一些功能外的特性。在使用外部庫的時候,在不查閱文件的基礎上,我們常常煩惱於如何呼叫api,一些引數非常容易混淆。舉乙個例子我們在學生系統中要對一名學生進行輸入生日的操作。item19常常我們使用a ...