首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

強制編譯期約束技術

     編輯:關於VC++

通用演算法常對處理對象施加特定限制。例如 std::sort() 需要元素定義 < 運算子。強制此約束很容易:編譯器嘗試針對給定型別呼叫該運算子,若不存在即產生編譯錯誤:

#include <algorithm>
struct S {}; // 未定義 operator<
int main()
{
    S s[2];
    std::sort(s, s+2); // 錯誤:型別 'S' 中未實作 'operator<'
}

但表達與強制執行抽象約束(如「必須是基類別」或「必須是 POD 型別」)並非易事,需要程式設計師更多彈性與技巧。本文示範如何以通用方式實現此類約束。

使用「約束模板」自動強制編譯期約束

問題情境

假設應用程式需要介面與非 C++ 模組(C 或 SQL 撰寫)互動,需保證傳遞給非 C++ 模組的所有物件具備 POD 型別(Plain Old Data,純舊資料型別)。

以下為 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 使用嚴格區分。<cstddef> 中標準巨集 offsetof() 即為例:

size_t nbytes = offsetof(S, mem);

此運算式以位元組為單位傳回成員 mem 的位移量。依 C++ 標準,S 必須是 POD 類別、結構或聯合,否則結果未定義。任務是撰寫約束,能在編譯期自動區分 POD 與非 POD 型別,一旦違反「必須是 POD 型別」約束,編譯器發出明確錯誤訊息。

實作約束

約束實際上是類別模板成員函式中的一個運算式或宣告。違反約束時,上述運算式觸發編譯錯誤。關鍵在於找到正確的編譯期運算式,在約束違反時啟動。熟悉 C++ 標準當然有益。標準規定非 POD 物件不能是聯合 (union) 成員(見標準 clause 9.5)。利用此限制,建立一個聯合,讓其唯一成員即為要測試的物件:

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

編譯器僅為實際被呼叫的成員函式產生程式碼(顯式或隱式)。因此在類別模板建構函式或解構函式實作約束,可保證編譯期處理每個實例。

測試程式碼:

// 以下三條通過編譯
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 物件,編譯器發出錯誤訊息,符合預期。

改進設計:避免執行期開銷

約束會導致執行期與空間開銷嗎?若在編譯期檢查,不想讓它留在可執行檔中。現代 IDE 很聰明,會優化掉可執行檔中不必要程式碼。為讓編譯器報錯,將約束移至獨立靜態成員函式 constraints()(函式名可自訂),宣告為 private 避免其他程式呼叫:

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

constraints() 實際上什麼都不做,僅宣告一個區域聯合型別變數。因該聯合變數未被使用,編譯器可省略建構函式中的 constraints(),避免不必要開銷。

其他常用約束範例

「必須是 T2 型別」約束:

// 檢查 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++ 標準委員會成員。業餘喜好古典音樂、維多利亞時期文學、自然語言研究、考古與地理。常至 C++ 論壇並定期為 C++ 網站與雜誌撰稿,亦在教育機構講授程式設計語言與應用語言課程。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved