国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

C++箴言:使接口易于正確使用難錯(cuò)誤使用

2019-11-17 05:24:22
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

  C++ 被沉沒(méi)于接口中。函數(shù)接口、類(lèi)接口、模板接口。每一個(gè)接口都意味著客戶(hù)的代碼和你的代碼互相影響。假設(shè)你在和通情達(dá)理的人打交道,那些客戶(hù)也想做好工作。他們想要正確使用你的接口。在這種情況下,假如他們犯了一個(gè)錯(cuò)誤,就說(shuō)明你的接口至少有部分是不完善的。在理想情況下,假如一個(gè)接口的一種嘗試的用法不符合客戶(hù)的預(yù)期,代碼將無(wú)法編譯,反過(guò)來(lái),假如代碼可以編譯,那么它做的就是客戶(hù)想要的。

  開(kāi)發(fā)易于正確使用,而難以錯(cuò)誤使用的接口需要你考慮客戶(hù)可能造成的各種錯(cuò)誤。例如,假設(shè)你正在設(shè)計(jì)一個(gè)代表時(shí)間的類(lèi)的構(gòu)造函數(shù):

class Date {
public:
Date(int month, int day, int year);
...
};
  匆匆一看,這個(gè)接口似乎是合乎情理的(至少在美國(guó)),但是客戶(hù)可能很輕易地造成兩種錯(cuò)誤。首先,他們可能會(huì)以錯(cuò)誤的順序傳遞參數(shù):

Date d(30, 3, 1995); // Oops! Should be "3, 30" , not "30, 3"
  第二,他們可能傳遞一個(gè)非法的代表月或日的數(shù)字:

Date d(2, 20, 1995); // Oops! Should be "3, 30" , not "2, 20"
 ?。ê竺孢@個(gè)例子看上去似乎沒(méi)什么,但是想想鍵盤(pán)上,2 就在 3 的旁邊,這種 "off by one" 類(lèi)型的錯(cuò)誤并不罕見(jiàn)。)

  很多客戶(hù)錯(cuò)誤都可以通過(guò)引入新的類(lèi)型來(lái)預(yù)防。確實(shí),類(lèi)型系統(tǒng)是你阻止那些不合適的代碼通過(guò)編譯的主要支持者。在當(dāng)前情況下,我們可以引入簡(jiǎn)單的包裝類(lèi)型來(lái)區(qū)別日,月和年,并將這些類(lèi)型用于 Data 的構(gòu)造函數(shù)。

strUCt Day { struct Month { struct Year {
eXPlicit Day(int d) explicit Month(int m) explicit Year(int y)
:val(d) {} :val(m) {} :val(y){}

int val; int val; int val;
}; }; };


class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
Date d(30, 3, 1995); // error! wrong types

Date d(Day(30), Month(3), Year(1995)); // error! wrong types

Date d(Month(3), Day(30), Year(1995)); // okay, types are correct
  將日,月和年做成封裝數(shù)據(jù)的羽翼豐滿(mǎn)的類(lèi)比上面的簡(jiǎn)單地使用 struct 更好,但是即使是 struct 也足夠證實(shí)明智地引入新類(lèi)型在阻止接口的錯(cuò)誤使用方面能工作得非常出色。

  只要放置了正確的類(lèi)型,它往往能合理地限制那些類(lèi)型的值。例如,月僅有 12 個(gè)合法值,所以 Month 類(lèi)型應(yīng)該反映這一點(diǎn)。做到這一點(diǎn)的一種方法是用一個(gè)枚舉來(lái)表現(xiàn)月,但是枚舉不像我們希望的那樣是類(lèi)型安全(type-safe)的。例如,枚舉能被作為整數(shù)使用。一個(gè)安全的解決方案是預(yù)先確定合法的 Month 的集合:

class Month {
public:
static Month Jan() { return Month(1); } // functions returning all valid
static Month Feb() { return Month(2); } // Month values; see below for
... // why these are functions, not
static Month Dec() { return Month(12); } // objects

... // other member functions

PRivate:
explicit Month(int m); // prevent creation of new
// Month values

... // month-specific data
};
Date d(Month::Mar(), Day(30), Year(1995));
  假如用函數(shù)代替對(duì)象來(lái)表現(xiàn)月的主意讓你感到驚異,那可能是因?yàn)槟阃朔蔷植快o態(tài)對(duì)象(non-local static objects)的初始化的可靠性是值得懷疑的。Item 4 能喚起你的記憶。

  防止可能的客戶(hù)錯(cuò)誤的另一個(gè)方法是限制對(duì)一個(gè)類(lèi)型能夠做的事情。施加限制的一個(gè)普通方法就是加上 const。例如,Item 3 解釋了使 Operator* 的返回類(lèi)型具有 const 資格是如何能夠防止客戶(hù)對(duì)用戶(hù)自定義類(lèi)型犯下這樣的錯(cuò)誤:

if (a * b = c) ... // oops, meant to do a comparison!
  實(shí)際上,這僅僅是另一條使類(lèi)型易于正確使用而難以錯(cuò)誤使用的普遍方針的一種表現(xiàn):除非你有很棒的理由,否則就讓你的類(lèi)型的行為與內(nèi)建類(lèi)型保持一致??蛻?hù)已經(jīng)知道像 int 這樣的類(lèi)型如何表現(xiàn),所以你應(yīng)該努力使你的類(lèi)型的表現(xiàn)無(wú)論何時(shí)都同樣合理。例如,假如 a 和 b 是 int,給 a*b 賦值是非法的。所以除非有一個(gè)非常棒理由脫離這種表現(xiàn),否則,對(duì)你的類(lèi)型來(lái)說(shuō)這樣做也應(yīng)該是非法的。

  避免和內(nèi)建類(lèi)型毫無(wú)理由的不相容的真正原因是為了提供行為一致的接口。很少有特性比一致性更易于引出易于使用的接口,也很少有特性比不一致性更易于引出令人郁悶的接口。STL 容器的接口在很大程度上(雖然并不完美)是一致的,而且這使得它們相當(dāng)易于使用。例如,每一種 STL 容器都有一個(gè)名為 size 的成員函數(shù)可以知道容器中有多少對(duì)象。與此對(duì)比的是 java,在那里你對(duì)數(shù)組使用 length 屬性,對(duì) String 使用 length 方法,而對(duì) List 卻要使用 size 方法,在 .NET 中,Array 有一個(gè)名為 Length 的屬性,而 ArrayList 卻有一個(gè)名為 Count 的屬性。一些開(kāi)發(fā)人員認(rèn)為集成開(kāi)發(fā)環(huán)境(IDEs)能補(bǔ)償這些瑣細(xì)的矛盾,但他們錯(cuò)了。矛盾在開(kāi)發(fā)者工作中強(qiáng)加的精神折磨是任何 IDE 都無(wú)法完全消除的。

  任何一個(gè)要求客戶(hù)記住某些事情的接口都是有錯(cuò)誤使用傾向的,因?yàn)榭蛻?hù)可能忘記做那些事情。例如,Item 13 介紹了一個(gè) factory 函數(shù),它返回一個(gè)指向動(dòng)態(tài)分配的 Investment 繼續(xù)體系中的對(duì)象的指針。


Investment* createInvestment(); // from Item 13; parameters omitted
// for simplicity
  為了避免資源泄漏,createInvestment 返回的指針最后必須被刪除,但這就為至少兩種類(lèi)型的客戶(hù)錯(cuò)誤創(chuàng)造了機(jī)會(huì):刪除指針失敗,或刪除同一個(gè)指針一次以上。

  我在前面展示了客戶(hù)可以怎樣將 createInvestment 的返回值存入一個(gè)類(lèi)似 auto_ptr 或 tr1::shared_ptr 智能指針,從而將使用 delete 的職責(zé)交給智能指針。但是假如客戶(hù)忘記使用智能指針呢?在很多情況下,一個(gè)更好的接口會(huì)預(yù)先判定將要出現(xiàn)的問(wèn)題,從而讓 factory 函數(shù)在第一現(xiàn)場(chǎng)即返回一個(gè)智能指針:

std::tr1::shared_ptr<Investment> createInvestment();
  這就從根本上強(qiáng)制客戶(hù)將返回值存入一個(gè) tr1::shared_ptr,幾乎完全消除了當(dāng)?shù)讓拥?Investment 對(duì)象不再使用的時(shí)候忘記刪除的可能性。

  實(shí)際上,返回一個(gè) tr1::shared_ptr 使得接口的設(shè)計(jì)者預(yù)防許多其它客戶(hù)的與資源泄漏相關(guān)的錯(cuò)誤成為可能,因?yàn)?,就?Item 14 解釋的:當(dāng)一個(gè)智能指針被創(chuàng)建的時(shí)候,tr1::shared_ptr 答應(yīng)將一個(gè)資源釋放(resource-release)函數(shù)——一個(gè) "deleter" ——綁定到智能指針上。(auto_ptr 則沒(méi)有這個(gè)能力。)

  假設(shè)從 createInvestment 得到一個(gè) Investment* 指針的客戶(hù)期望將這個(gè)指針傳給一個(gè)名為 getRidOfInvestment 的函數(shù),而不是對(duì)它使用 delete。這樣一個(gè)接口又為一種新的客戶(hù)錯(cuò)誤打開(kāi)了門(mén),這就是客戶(hù)可能使用了錯(cuò)誤的資源析構(gòu)機(jī)制(也就是說(shuō),用了 delete 而不是 getRidOfInvestment)。createInvestment 的實(shí)現(xiàn)可以通過(guò)返回一個(gè)在它的 deleter 上綁定了 getRidOfInvestment 的 tr1::shared_ptr 來(lái)預(yù)防這個(gè)問(wèn)題。

  tr1::shared_ptr 提供了一個(gè)需要兩個(gè)參數(shù)(要被治理的指針和當(dāng)引用計(jì)數(shù)變?yōu)榱銜r(shí)要調(diào)用的 deleter)的構(gòu)造函數(shù)。這里展示了創(chuàng)建一個(gè)以 getRidOfInvestment 為 deleter 的 null tr1::shared_ptr 的方法:

std::tr1::shared_ptr<Investment> // attempt to create a null
pInv(0, getRidOfInvestment); // shared_ptr with a custom deleter;
// this won’t compile
  唉,這不是合法的 C++。tr1::shared_ptr 的構(gòu)造函數(shù)果斷要求它的第一個(gè)參數(shù)應(yīng)該是一個(gè)指針,而 0 不是一個(gè)指針,它是一個(gè) int。當(dāng)然,它能轉(zhuǎn)型為一個(gè)指針,但那在當(dāng)前情況下并不夠好用,tr1::shared_ptr 果斷要求一個(gè)真正的指針。用強(qiáng)制轉(zhuǎn)型解決這個(gè)問(wèn)題:

std::tr1::shared_ptr<Investment> // create a null shared_ptr with
pInv(static_cast<Investment*>(0), // getRidOfInvestment as its
getRidOfInvestment); // deleter; see Item 27 for info on
// static_cast
  據(jù)此,實(shí)現(xiàn)返回一個(gè)以 getRidOfInvestment 作為 deleter 的 tr1::shared_ptr 的 createInvestment 的代碼看起來(lái)就像這個(gè)樣子:

std::tr1::shared_ptr<Investment> createInvestment()
{
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),
getRidOfInvestment);

retVal = ... ; // make retVal point to the
// correct object

return retVal;
}
  當(dāng)然,假如將被 pInv 治理的裸指針可以在創(chuàng)建 pInv 時(shí)被確定,最好是將這個(gè)裸指針傳給 pInv 的構(gòu)造函數(shù),而不是將 pInv 初始化為 null 然后再賦值給它。。

  tr1::shared_ptr 的一個(gè)非凡好的特性是它自動(dòng)逐指針地使用 deleter 以消除另一種潛在的客戶(hù)錯(cuò)誤——“cross-DLL 問(wèn)題。”這個(gè)問(wèn)題發(fā)生在這種情況下:一個(gè)對(duì)象在一個(gè)動(dòng)態(tài)鏈接庫(kù)(dynamically linked library (DLL))中通過(guò) new 被創(chuàng)建,在另一個(gè)不同的 DLL 中被刪除。在許多平臺(tái)上,這樣的 cross-DLL new/delete 對(duì)會(huì)引起運(yùn)行時(shí)錯(cuò)誤。tr1::shared_ptr 可以避免這個(gè)問(wèn)題,因?yàn)樗娜笔〉?deleter 只將 delete 用于這個(gè) tr1::shared_ptr 被創(chuàng)建的 DLL 中。這就意味著,例如,假如 Stock 是一個(gè)繼續(xù)自 Investment 的類(lèi),而且 createInvestment 被實(shí)現(xiàn)如下,

std::tr1::shared_ptr<Investment> createInvestment()
{
return std::tr1::shared_ptr<Investment>(new Stock);
}
  返回的 tr1::shared_ptr 能在 DLL 之間進(jìn)行傳遞,而不必關(guān)心 cross-DLL 問(wèn)題。指向這個(gè) Stock 的 tr1::shared_ptr 將保持對(duì)“當(dāng)這個(gè) Stock 的引用計(jì)數(shù)變?yōu)榱愕臅r(shí)候,哪一個(gè) DLL 的 delete 應(yīng)該被使用”的跟蹤。

  本文不是關(guān)于 tr1::shared_ptr 的——而是關(guān)于使接口易于正確使用,而難以錯(cuò)誤使用的——但 tr1::shared_ptr 正是這樣一個(gè)消除某些客戶(hù)錯(cuò)誤的簡(jiǎn)單方法,值得用一個(gè)概述來(lái)看看使用它的代價(jià)。最通用的 tr1::shared_ptr 實(shí)現(xiàn)來(lái)自于 Boost(參見(jiàn) Item 55)。Boost 的 shared_ptr 的大小是裸指針的兩倍,將動(dòng)態(tài)分配內(nèi)存用于簿記和 deleter 專(zhuān)用(deleter-specific)數(shù)據(jù),當(dāng)調(diào)用它的 deleter 時(shí)使用一個(gè)虛函數(shù)來(lái)調(diào)用,在一個(gè)它認(rèn)為是多線(xiàn)程的應(yīng)用程序中,當(dāng)引用計(jì)數(shù)被改變,會(huì)導(dǎo)致線(xiàn)程同步開(kāi)銷(xiāo)。(你可以通過(guò)定義一個(gè)預(yù)處理符號(hào)來(lái)使多線(xiàn)程支持失效。)在缺點(diǎn)方面,它比一個(gè)裸指針大,比一個(gè)裸指針慢,而且要使用輔助的動(dòng)態(tài)內(nèi)存。在許多應(yīng)用程序中,這些附加的運(yùn)行時(shí)開(kāi)銷(xiāo)并不顯著,而對(duì)客戶(hù)錯(cuò)誤的減少卻是每一個(gè)人都看得見(jiàn)的。

  Things to Remember

  ·好的接口易于正確使用,而難以錯(cuò)誤使用。你應(yīng)該在你的所有接口中為這個(gè)特性努力。

  ·使易于正確使用的方法包括在接口和行為兼容性上與內(nèi)建類(lèi)型保持一致。

  ·預(yù)防錯(cuò)誤的方法包括創(chuàng)建新的類(lèi)型,限定類(lèi)型的操作,約束對(duì)象的值,以及消除客戶(hù)的資源治理職責(zé)。

  ·tr1::shared_ptr 支持自定義 deleter。這可以防止 cross-DLL 問(wèn)題,能用于自動(dòng)解鎖互斥體等。


發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 扬州市| 衡水市| 康马县| 日照市| 中江县| 镇远县| 祁连县| 谢通门县| 邵阳县| 宁德市| 泾川县| 松江区| 石屏县| 敖汉旗| 隆回县| 乐业县| 富源县| 开平市| 东乡族自治县| 黄山市| 樟树市| 贺州市| 潍坊市| 凌海市| 澎湖县| 乃东县| 东兴市| 乐昌市| 合山市| 舞阳县| 稷山县| 明水县| 阿拉尔市| 南平市| 乌拉特后旗| 麦盖提县| 凤山市| 老河口市| 盐亭县| 义乌市| 宁蒗|