程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 強制編譯時約束

強制編譯時約束

編輯:關於VC++

通用的約束和算法常常給所處理的對象強加某些限制。例如,std::sort()算法需要其操 作的對象元素定義 < 操作符。強制此約束很容易:編譯器嘗試針對給定類型調用該操作 符。如果這個操作符不存在,你會得到一個編譯錯誤:

#include <algorithm>
struct S{}; //doesn't define operator <
int main()
{
   S s[2];
   std::sort(s, s+2); // 出錯: 在類型 'S'中 'operator<' 沒有實現
}

但是,要表達約束以 及強制執行約束並不是一件容易的事情。很多抽象的約束比如:“必須是一個基類 ”或“必須是一個 POD 類型”需要來自程序員更多的靈活性和技巧。本文 下面將示范如何一通用的方式實現此類約束。

如何以通用的方式強制執行對象的編 譯時約束?

使用“約束模板”自動強制執行編譯時約束

提出問題

假設你的應用程序需要一個接口,這個接口是用 C 或者 SQL 編寫的非C++模塊。為 此,你需要保證傳遞到非C++模塊的所有對象具備 POD 類型。

struct S1 { int x;};
class S2 { public: void func(); };
union S3 { struct { int x, y; } t; char c[4];};
struct S4 : S1, S2 {};

以上數據都是 POD 類型, 而下面這些則不然:

struct C1 {
    virtual void func(); //有 一個虛函數
};
struct C2 {
    struct T{ int x, y; };
     ~C2(); //有一個析構函數
};
struct C3 : virtual S1 {} ; //有一個虛擬 基類

在個別編程實現中 POD 和 非 POD 的使用是有嚴格區分的,在 <csstddef> 中定義的標准宏 offsetof() 就是一個例子。參見下列表達式:

size_t nbytes = offsetof (S, mem);

該表達式以字節為單位返回 成員 mem 的偏移量。按照 C++ 標准,S 必須是一個 POD 類(class),結構(struct)或 者聯合(union),否則,結果是不確定的。所以你的任務是編寫一個約束,這個約束能在編 譯時自動區分 POD 和 非 POD 類型。一旦違反了“必須是一個 POD 類型”約束 ,編譯器便會發出明確的出錯信息。

實現約束

約束實際上就是在某個類模板 的成員函數中的一個表達式或者是一個聲明。當約束被違反後,上述的表達式便觸發一個編 譯錯誤。具有挑戰的地方是要找到正確的編譯時表達式,在約束被違反時激活這些表達式。 此時熟悉 C++ 標准當然是有益而無害的。標准中說非 POD 對象不能是一個聯合(union)的 成員(見標准的 clause 9.5)。利用這個限制,創建一個聯合,讓其唯一成員就是你要測試 的對象不就行了。

template <class T> struct POD_test
{
  POD_test()
  {
    union
    {
      T t; //T 必須是一個 POD 類型
    } u;
  }
};

編譯器只 為實際被調用的成員函數產生代碼,或者顯式或者隱式。因此,在某個類模板的構造函數或 吸構函數中實現該約束將保證其編譯時能處理其每個實例。(稍後我們將看到如何改進此設 計)

為了測試這段代碼,你可以使用各種不同的模版參數來進行實例化:

//下列三條語句通過編譯
POD_test <int> pi;
POD_test <S1> ps1;
POD_test <S4> ps4;
//編譯失敗
POD_test <std::string> pstr;
POD_test <C1> pc1;
POD_test <C2> pc2;

正像我們期望的那樣,由於後面三個實例其模板參數不是 POD 對象,編譯器 在處理時發出了出錯信息。

改進設計

約束能導致運行時和空間上的開銷嗎? 如果它在編譯時檢查,你肯定不想讓它呆在可執行文件中,現代 IDEs 都很聰明, 將可執行 文件中不必要的代碼優化掉。為了讓編譯器報錯,將約束移到一個單獨的靜態成員函數 constraints() 中(你也可以另外的函數名)。記住聲明時時這個成員是 private,以便其 它程序無法調用它:

template <class T> struct POD_test
{
  POD_test(){constraints();} // 強制編譯時處理
private:
  static void constraints()
  {
    union{ T t;} u;
  }
};

注意 constraints() 函數實際上什麼也沒做,它只是聲明了一個局部聯合類 型的變量。由於該聯合變量沒有被使用,編譯器可以省略掉這個構造函數中的 constraints (),從而避免了必要的開銷。

“必須是 POD ” 只是眾多強制約束中一個 案例。另一個常用的約束是“必須是 T”,實現這個約束也非常簡單:

//檢查 T1 是否為 T2
template <class T1, class T2> struct is_a_T2
{
  is_a_T2() {constraints();}
  static void constraints()
  {
    T1 t1;
    T2& ref=t1; // 如果 t1 不是 t2 則出錯
  }
};

這裡是另外一個約束:“必須是一 個整型”,這個也不難,有多種技術途徑來實現。其中之一就是是使用該對象作為某個 數組的下標。因為 C++ 需要整型作為下標,使用任何其它的類型都將導致編譯錯誤。我將這 個實現留給讀者來做。

Danny Kalev 是一名通過認證的系統分析師,專攻 C++ 和形 式語言理論的軟件工程師。1997 年到 2000 年期間,他是 C++ 標准委員會成員。最近他以 優異成績完成了他在普通語言學研究方面的碩士論文。 業余時間他喜歡聽古典音樂,閱讀維 多利亞時期的文學作品,研究 Hittite、Basque 和 Irish Gaelic 這樣的自然語言。其它興 趣包括考古和地理。Danny 時常到一些 C++ 論壇並定期為不同的 C++ 網站和雜志撰寫文章 。他還在教育機構講授程序設計語言和應用語言課程。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved