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

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

C++ 泛型編程系列講座之實施

2019-11-17 05:40:15
字體:
供稿:網(wǎng)友

  你知道,當(dāng)一個概念從一個專有名詞變成一個普通名詞時,說明它真正的深入人心了。比如Kleenex(面巾紙品牌,也指面巾紙),Xerox(施樂,復(fù)印機(jī)品牌,也指復(fù)印機(jī))Q-Tips(化妝品品牌,也指化妝包),對嗎?所以說,當(dāng)我聽說你可以在Visual C++.NET中使用“modern C++ design”時非常興奮也就不希奇了。這里指的是——至少我這樣認(rèn)為——《Modern C++ Design》[1]所推行的基于模板的技術(shù)。

  在所有泛型編程文章中都有一個成功的要素,就是進(jìn)行特有至通用的“文法升華。”比如,在ScopeGuard[2]中增加了放入“撤消”動作于正常執(zhí)行路徑而當(dāng)一個復(fù)雜操作成功時解除“撤消”的常見技術(shù)之后,ScopeGuard就成為了“域守衛(wèi)(scope guard)”。我大多數(shù)受歡迎的文章不完全由我完成,而是和Petru Marginean緊密合作的結(jié)果。而且讓我更加興奮的是再一次在本文中與他合作。

  本文中我們將討論對應(yīng)于發(fā)布狀態(tài)的機(jī)制:實施(enforcement),這是方便實用的快速條件驗證機(jī)制。非常類似于ScopeGuard,ENFORCE宏極大程度地減少了你需要使用在錯誤處理上的代碼。ScopeGuard和ENFORCE可相互獨立工作,但最好是一起使用。ENFORCE是意外的源頭,ScopeGuard是意外的傳播者。

  實施(Enforcements)

  設(shè)想你注視著面前的一大堆代碼。你知道你要在這些代碼中殺出一條血路,你也知道你必須寫更多的新代碼。

  一個好習(xí)慣是先瀏覽代碼,試著找到一些通用模式。你要理解模式背后的概念。很有可能,所有這些都不是偶然發(fā)生的,而是同樣的基本概念的不同表現(xiàn)形式,然后,你可以整理這些提取出的模式,這樣你就可以扼要地表達(dá)所有的作為概念的實現(xiàn)體的模式

  現(xiàn)在先暫停,看一下你要分析的不同模式的大小。假如這個模式很大,比如作為基礎(chǔ)部分涉及到了整個程序,那么你正在和結(jié)構(gòu)模式(architectural patterns)打交道。

  假如模式是中等尺寸,橫跨數(shù)個對象和/或函數(shù),那么你碰到的是設(shè)計模式。

  假如模式非常小,只包括3-10行代碼,那么你面前的的常用法(idiom)。

  最后,假如模式只有1-2行代碼,你得到的東西叫做代碼風(fēng)格或格式。

  這四種根據(jù)規(guī)模劃分的類型所涵蓋的范圍類似于建筑學(xué)。在建筑學(xué)中,基本結(jié)構(gòu)的小瑕疵不要緊。在軟件架構(gòu)中,任何缺陷都可能毀掉整個“建筑”。相反,你需要在任何范圍使用正確的技術(shù)來確保成功。假如你對小細(xì)節(jié)著迷而忽略大的藍(lán)圖,你會浪費才華于建立不能遠(yuǎn)觀的復(fù)雜的阿拉伯式建筑。假如你只注重大的方面而忽視細(xì)節(jié),你會得到粗糙的龐然大物。

  這是寫軟件異常困難的原因,這個困難是在其他領(lǐng)域工作的人們不能完全理解的。

  實施屬于常用法范疇。具體點說,實施極大簡化了錯誤檢測代碼而不會影響可讀性和正常工作流的流暢性。

  想法來源于下列事實。你拋出一個意外非??赡苁亲鳛橐粋€布爾值檢測的結(jié)果,如下:

if (some test)
 throw SomeException(arguments);
  假如該意外在多處出現(xiàn),為什么不把它放在一個小函數(shù)里呢:

template <class E, class A>
inline void Enforce(bool condition A arg)
{
 if (!condition) throw E(arg);
}
  可以這樣使用它:

Widget* p = MakeWidget();
Enforce<std::runtime_error>(p != 0, “null pointer”);
Enforce<std::runtime_error>(cout != 0, “cout is in error”);
cout << p->ToString();
  目前為止一切順利.現(xiàn)在,我們來分析幾個重要方面。

  首先,被測條件不總是布爾型的,也可能是一個指針或整型。其次,非常可能你要在檢測完之后馬上使用被測值。比如,你可能希望在使用前確保一個指針非空,或你可能要在創(chuàng)建一個文件句柄后馬上使用它。所以我們修改Enforce,這樣它就有濾過機(jī)制來把接受的值傳回:

template <class E, class A, class T>
inline T& Enforce(T& obj, A arg)
{
 if (!obj) throw E(arg);
  return obj;
}

template <class E, class A, class T>
inline const T& Enforce(const T& obj, A arg)
{
 if (!obj) throw E(arg);
  return obj;
}
  (兩個版本是必須的,分別對應(yīng)const和非const對象。)你可以增加兩個重載版本來表示你通常會拋出的那種意外和它所帶參數(shù)。

Template <class T>
Inline T& Enforce(T& obj, const char* arg)
{
 return this->Enforce<std::runtime_error, const char*, T>(obj,arg);
}

template <class T>
inline const T& Enforce(const T& obj, const char* arg)
{
 return this->Enforce<std::runtime_error, const char*, T>(obj,arg);
}
  假如你認(rèn)為應(yīng)該傳入一個通用參數(shù)(信息)到std::runtime_error,調(diào)用可以進(jìn)一步簡化。你所需做的一些只是增加幾個重載函數(shù):


Template <class T>
Inline T& Enforce(T& obj)
{
 return this->EnforceMstd::runtime_error, const char*, T>(obj,“Enforcement error”);
}

template <class T>
inline const T& Enforce(const T& obj)
{
 return this->Enforce<std::runtime_error, const char*, T>(obj,“Enforcement error”);
}
  現(xiàn)在,隨著這些簡單的擴(kuò)展,代碼變得相當(dāng)?shù)糜斜憩F(xiàn)力:

Enforce(cout) << Enforce(MakeWidget())->ToString();
  在一行中你不單創(chuàng)建了一個widget對象并把它打印到控制臺,你還標(biāo)明在這個過程中可能會發(fā)生的任何錯誤!你也許還要自動釋放創(chuàng)建的Widget對象,只需要在里面添加auto_ptr:

Enforce(cout) <<
Enforce(auto_ptr(MakeWidget()))->ToString();
  哇!非常好——非凡是拿它和其他解決方案相比較時。

  在不中斷正常執(zhí)行流程的情況下,Enforce漂亮地過濾掉錯誤。這樣,Enforce提供了一個方便的手段來檢查和清除錯誤情況。

  盡量讓程序員方便地處理錯誤是非常重要的。這是因為錯誤處理經(jīng)常,不幸的,被認(rèn)為是白費力氣的工作。經(jīng)理們不會把錯誤處理作為考核標(biāo)準(zhǔn)。結(jié)果是,匆忙的,過度操勞的,預(yù)備不足的[3]程序員們在那咬著手指頭期盼cout會總是處于一個良好的狀態(tài),MakeShape也決不返回空指針。但咬手指頭并不是一個真正好的編程方法。 修飾Enforce

  上面代碼中所顯示的“Enforcement failed”信息并不是很有用,所以我們需要對它做點修改。幸運(yùn)的是,Petru的靈感一發(fā)而不可收拾?!澳莻€腦袋從不停止工作!”

  首先,包含在錯誤通知中的好的信息應(yīng)該有不討人喜歡的__FILE__和__LINE__。同時,能看到失敗的表達(dá)式也會很有幫助。就象我們在Asserter[4]中所做的,我們建立一個小小的類來為我們保存這些信息:

template <class Ref>
class Enforcer
{
 Ref obj_;
 const char* const locus_;
 public:
  Enforcer(Ref obj, const char* locus) : obj_(obj), locus_(locus) {}
  Ref Enforce()
  {
   if (!obj_) throw std::runtime_error(locus_);
    return obj_;
  }
};
  obj_成員保存被檢測的對象。Locus_成員是上述關(guān)于文件,行數(shù),和表達(dá)式的信息。

  為什么我們把Enforcer的模板參數(shù)叫做Ref而不是傳統(tǒng)的T?原因是我們要總是用一個引用類型(不是一個值類型)來實例化Enforce,這會在接下去減少我們很多重復(fù)勞動。(假如你曾經(jīng)寫過類似的對const和非const引用的函數(shù),你就會知道我的意思)

  好吧,現(xiàn)在創(chuàng)建Enforcer對象,我們用一個小函數(shù),這樣我們可以輕松一點,讓它來做類型推斷:

template <typename T>
inline Enforcer<const T&>
MakeEnforcer(const T& obj, const char* locus)
{
 return Enforcer<const T&>(obj, locus);
}

template <typename T>
inline Enforcer<T&>
MakeEnforcer(const T& obj, const char* locus)
{
 return Enforcer<T&>(obj, locus);
}

  我們現(xiàn)在只需要給蛋糕裱上奶油——意料之中的宏。

  我們知道你討厭宏,而且討厭宏的不在少數(shù),但我們更討厭重復(fù)打__FILE__和__LINE__:

#define STRINGIZE(something) STRINGIZE_HELPER(something)
#define STRINGIZE_HELPER(something) #something
#define ENFORCE(eXP) /
MakeEnforcer((exp), "Ex__FILE__ "', line: "STRINGIZE(__LINE__)).Enforce()
  STRINGIZE和STRINGIZE_HELPER宏是預(yù)編譯器必須的復(fù)雜過程來把__LINE__轉(zhuǎn)換為數(shù)字。(不,#__LINE__沒有用。)我從來都不完全知道這些宏怎樣和為什么起作用(這和預(yù)編譯器部分有關(guān)…啊,我腦海里開始涌現(xiàn)那悲慘的回憶!停下來,醫(yī)生?。?,坦白說,我情愿去了解紐約城的下水道系統(tǒng)怎樣運(yùn)作也不想知道這里的細(xì)節(jié)。只要說STRINGIZE(__LINE__)產(chǎn)生了一個包含現(xiàn)在行數(shù)的字符串就足夠了。那些這方面的專家[6]提供了完整的解釋。

  本專欄的一貫傳統(tǒng)是不涉及編譯器特性,所以我們只順便提一下STRINGIZE技巧在MSVC的預(yù)編譯器上會產(chǎn)生類似于(__LINE__VAR+7)的神秘字符串。

  令人興奮的一面是,Enforcer的初始化代價只有兩個指針賦值那樣低廉,同時卻保存了非常有用的信息。你可以方便地增加關(guān)于文件日期和編譯時間的信息,以及非標(biāo)準(zhǔn)的信息,比如__FUNCTION__。

  支持多個參數(shù)及ENFORCE的自定義判定條件

  ENFORCE是個很好的想法,但假如你用了某樣?xùn)|西卻發(fā)現(xiàn)在實際應(yīng)用中卻不如文章中宣稱的那么有用,你不會感到受欺騙了嗎?

  我們會,并且我們已經(jīng)發(fā)現(xiàn)ENFORCE中兩個重要缺陷。

  首先,通常更需要在默認(rèn)的文件名,行數(shù),和表達(dá)式信息的基礎(chǔ)上增加——或代之以——傳入一個自定義字符串的功能。

  其次,ENFORCE只用!操作符來檢測非零條件。然而,在真實應(yīng)用中,有時候需要被檢查的“錯誤”值不是零。許多使用整數(shù)返回值的API,包括在<io.h>中的標(biāo)準(zhǔn)C文件函數(shù),返回-1來標(biāo)識一個錯誤。另一些API使用一個符號常量。而COM使用更復(fù)雜的情況:假如返回值為零(就是S_OK),表示正常,假如返回值小于零,說明有一個錯誤,并且返回的實際值給出了錯誤的信息。假如返回值大于零,狀態(tài)就是“帶信息的成功”,就是說返回值中有一些有用的信息[5]。

  顯然我們需要一個更靈活的檢測和報告框架。我們需要能夠在兩個層面上配置實施(判定條件和參數(shù)傳入機(jī)制),最好在編譯時配置,這樣實施機(jī)制比同等的手寫代碼不會有更多的開銷。(有一個明智的檢查總是需要做一下:當(dāng)某種抽象應(yīng)用于具體情況,是否能比得上非抽象的解決方法?)

  基于策略的(Policy-based)設(shè)計正適合于解決此問題。所以Enforce需要從一個簡單的類改進(jìn)為一個雙策略參數(shù)的模板類。第一個策略是判定條件策略(處理檢測事宜),第二個策略是拋出策略(處理構(gòu)建和拋出意外對象)。


template<typename Ref, typename P, tyoename R>
class Enforcer
{
 …使用兩個策略(看下一部分)…
}
  兩個策略都有非常簡單的接口。以下是默認(rèn)策略:

strUCt DefaultPredicate
{
 template <class T>
 static bool Wrong(const T& obj)
 {
  return !obj;
 }
}
struct DefaultRaiser
{
 template <class T>
 static void Throw(const T&, const std::string& message, const char* locus)
 {
  throw std::runtime_error (message + ‘/n’ + locus);
 }
} 實現(xiàn)細(xì)節(jié)(和漂亮的技巧)

  好的,現(xiàn)在讓Enforcer使用它的兩個策略來檢測值并拋出意外應(yīng)該很簡單。

  當(dāng)出現(xiàn)錯誤時假如能讓用戶來指定任意信息格式將會非常有用;進(jìn)一步,除非一個意外確實被拋出,這些信息(可能會影響運(yùn)行速度)應(yīng)該被避免。一些靈感加99%的汗水,我們設(shè)計了一個能夠滿足這些要求的機(jī)制。

  我們先展示代碼然后做說明。最終的Enforcer類如下所示:

template <typename Ref, typename P, typename R>
class Enforcer
{
 public:
  Enforcer(Ref t, const char* locus) : t_(t), locus_(P::Wrong(t) ? locus : 0)
  {
  }
  Ref
Operator*() const
  {
   if (locus_) R::Throw(t_, msg, locus_);
    return t_;
  }
  template <class MsgType>
  Enforcer& operator()(const MsgType& msg)
  {
   if (locus_)
   {
    //執(zhí)行到這里我們就有的是時間,不必有太高效率
    std::ostringstream ss;
    ss <<msg;
    msg_ += ss.str ();
   }
   return *this;
  }
  private:
   Ref t_;
   std::string msg_;
   const char* const locus_;
 };
 template <class P, class R, typename T>
 inline Enforcer<const T&, P, R>
 MakeEnforcer(const T& t, const char* locus)
 {
  return Enforcer<const T&, P, R>(t, locus);
 }
 template <class P, class R, typename T>
 inline Enforcer<T&, P, R>
 MakeEnforcer(T& t, const char* locus)
 {
  return Enforcer<T&, P, R>(t, locus);
 }
 #define ENFORCER(exp) /
 *MakeEnforcer<DefaultPredicate, DefaultRaiser>(/(exp), “Expression ‘” #exp “’ failed in ‘ ” /
__FILE__ ‘”, line: “ STRINGIZE(__LINE__))

  非常好,這樣Enforce定義了兩個操作符函數(shù):operator*和模板化的operator()。而且注重ENFORCE宏將“*”放在MakeEnforcer調(diào)用之前。所有這些的工作原理是什么?為什么要這些輔助代碼?

  假設(shè)你寫下如下代碼:

Widget* pWidget = MakeWidget();
ENFORCE(pWidget);
  ENFORCE宏擴(kuò)展為:

*MakeEnforcer<DefaultPredicate, DefaultRaiser>(fpWidget),“Expression ‘pWidget’ failed in ‘blah.cpp’, line: 7”)
  MakeEnforcer被調(diào)用后創(chuàng)建下面類型的一個對象:

Enforcer<const Widget*&, DefaultPredicate, DefaultRaiser>
  該對象的創(chuàng)建使用了兩個參數(shù)的構(gòu)造函數(shù)。請注重,locus_只有當(dāng)P::Wrong(t)為真時才被初始化為一個非空指針。換句話說,locus_只有在意外應(yīng)該被拋出時才指向有用信息,否則為空。

  對于被創(chuàng)建的對象調(diào)用operator*函數(shù)。不出意料的,假如locus_非空,R::Throw被調(diào)用,否則被檢測的對象只是被傳回。

  繼續(xù)看一個更有趣的例子,代碼如下:

Widget* pWidget = MakeWidget ();
ENFORCE(pWidget)(“This widget is null and it shouldn’t!”);
  此處當(dāng)Enforcer對象被創(chuàng)建后,operator()被調(diào)用。該操作要么把傳入信息添加到msg_成員變量中,要么假如pWidget非空即無錯誤出現(xiàn)就忽略所有。換句話說,正常執(zhí)行路徑與帶一個檢測的執(zhí)行路徑執(zhí)行得一樣快。這就是漂亮的地方——真正的工作只在發(fā)生錯誤的情況才做,

  因為operator()是模板化的并且使用一個std::ostringstream,它支持一切你可以傳到cout去的東西。而且,operator()返回*this,這樣你可以把對它的連續(xù)調(diào)用連接起來,下面例子展示了這一點:


int n = …;
Widget* pWidget = MakeWidget(n);
ENFORCE(pWidget)(“Widget number “)(n)(“ is null and it shouldn’t!”);
  我們不知道你感覺如何,反正我們對這個設(shè)計十分的滿足。反過來說,誰會不喜歡一個簡明的,富于表達(dá)力的,并且是高效的解決方案呢? 定制判定和拋出策略

  基于策略的Enforcer提供了重要的鉤子來答應(yīng)無限的變化。比如,檢測句柄值不為-1(而不是檢測不為零)只需要六行代碼:

struct HandlePredicate
{
 static bool Wrong(long handle)
 {
  return handle == -1;
 }
};

#define HANDLE_ENFORCE(exp)/
*MakeEnforcer<HandlePredicate, DefaultRaiser>((exp), “Expression ‘” #exp “’ failed in ‘” / __FILE__”’, line : “ STRINGIZE(__LINE__))
  別忘了Enforce返回傳入的值,這給了客戶代碼帶來極大的表現(xiàn)力:

const int available = HANDLE_ENFORCE(_read(file, buffer, buflen));
  上面從一個文件讀取數(shù)據(jù),記錄讀取的字節(jié)數(shù),并且標(biāo)志可能出現(xiàn)的錯誤。一行搞定,酷吧!

  類似的,你能夠為你的應(yīng)用程序方便弟定義適合的新策略和XYZ_ENFORCE宏。對應(yīng)每個錯誤編碼規(guī)范可以有一個XYZ_ENFORCE宏。多數(shù)應(yīng)用程序使用1到4種不同規(guī)范。實際操作中我們碰到過下列常用規(guī)范:

  1、我們上面討論過的基本ENFORCE。用operator!檢查并且拋出std::runtime_exception。

  2、HANDLE_ENFORCE。檢測不為-1并且拋出某種意外。

  3、COM_ENFORCE。當(dāng)結(jié)果為負(fù)就作為錯誤處理。拋出策略從COM返回代碼中提取錯誤信息并且放入要被拋出的意外對象中。我們相信這個工具在寫重要的COM應(yīng)用程序時是真正的無價之寶

  4、CLIB_ENFORCE。許多在C標(biāo)準(zhǔn)庫中的函數(shù)錯誤時返回零并要你來檢查errno獲知錯誤詳情。假如有一個能夠把errno轉(zhuǎn)為文本并將文本放入要拋出的意外中的漂亮的Raiser策略,CLIB_ENFORCE就會非常完美。

  5、WAPI_ENFORCE。某些Windows API函數(shù)成功時返回零失敗時返回負(fù)的錯誤代碼。

  適應(yīng)新的錯誤編碼規(guī)范是一件非常簡單的事情。

  結(jié)論

  我們發(fā)現(xiàn)值的自動實施檢查極其有用,甚至于擁有它等于擁有樂趣,失去它寸步難行。實施不是把幾行代碼濃縮為一行代碼那么簡單。實施讓你集中精力于應(yīng)用的正常執(zhí)行流,自然而靈活地過濾掉不需要的值。這種過濾是通過返回其傳入?yún)?shù)的過濾函數(shù)實現(xiàn)的。

  用了幾個宏技巧,付出了非常低的運(yùn)行時開銷,增加了有用信息

  通過模板參數(shù)的參數(shù)化實現(xiàn)了基于策略的設(shè)計,它不僅強(qiáng)大在理論上,而且實踐中同樣如此?;诓呗缘姆椒óa(chǎn)生的設(shè)計有低運(yùn)行時開銷(和手寫的if同樣小)和能夠適應(yīng)大多數(shù)非凡錯誤編碼規(guī)范的高可配置框架。

  我們已用Microsoft Visual C++.NET Everett Beta和gcc 3.2測試過附加代碼。放心用吧!

  參考和注釋

  [1] A. Alexandrescu. Modern C++ Design (Addison-Wesley Longman, 2001).
  [2] Andrei Alexandrescu and Petru Marginean. "Simplify your Exception-Safe Code"
  [3] 預(yù)備不足意思是讓一名程序員接手先天不足的具體設(shè)計。
  [4] Andrei Alexandrescu. "Assertions"
  [5] 這個規(guī)范看上去很好。但不知什么原因演變成舊規(guī)范。在COM世界中,S_TRUE是零,S_FALSE是1。不要忽略這個信息!
  [6] http://www.jaggersoft.com/pubs/CVu10_1.Html

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 巨鹿县| 南乐县| 连江县| 龙山县| 沙河市| 昭通市| 德州市| 临夏县| 新野县| 西峡县| 新化县| 田林县| 龙游县| 益阳市| 江永县| 长宁区| 中阳县| 乳山市| 婺源县| 浦城县| 城步| 桂东县| 泽库县| 昆山市| 武陟县| 玉屏| 宁乡县| 曲阜市| 富民县| 嘉兴市| 光泽县| 抚松县| 瑞安市| 红桥区| 肃北| 礼泉县| 青冈县| 鄂托克前旗| 隆林| 黎城县| 漯河市|