利用 C/C++ 編譯器對源程序進行編譯的時候會檢查語法錯誤和計算常量等特性,可以給我們的 C/C++源代碼添加一些編譯期的契約,要求源代碼按一定的規則使用,這樣的好處是可以減少很多使用錯誤,減少軟件的 bug,因為這些 bug 在代碼編譯的時候就發現了,不用等測試或現場使用時才發現。
下面舉一些編譯期的契約,約束例子。
1. must_have_base()必須繼承自
template<typenameD, typename B>
structmust_have_base
{
~must_have_base()
{
void (*p)(D*,B*) = constraints;
}
private:
static void constraints(D*pd, B* pb)
{
pb = pd;
}
};
模板原理:子類指針可以直接賦值給基類指針。
使用場景舉例:
class Base
{
public:
Base();
};
template <classT>
class C : must_have_base<T,Base>
{ /* ... */ };
這個例子要求你使用模板類 C時,模板參數必須繼承自類 Base。
2. must_be_subscriptable()必須可以按下標方式訪問
template<typenameT>
structmust_be_subscriptable
{
static void constraints(Tconst &T_is_not_subscriptable)
{
sizeof(T_is_not_subscriptable[0]);
}
};
模板原理:就是按下標方式來訪問進行檢驗。
使用場景舉例:
template<classT>
void fun(T& t)
{
must_be_subscriptable<T>constraint;
// t[0] ?
// ...
}
要求輸入參數要求可以用下標來訪問
3. must_be_subscriptable_as_decayable_pointer()必須可以按下標方式訪問,並且可以退化為原生指針
template<typenameT>
structmust_be_subscriptable_as_decayable_pointer
{
static void constraints(Tconst &T_is_not_decay_subscriptable)
{
sizeof(0[T_is_not_decay_subscriptable]);
}
};
模板原理:原生指針的特性,通過下標訪問可以反過來,就是即可以這樣 pointer[offset] 使用指針,也可以這樣 offset[pointer]使用指針。
4. must_be_pod()必須為 POD類型
POD類型:POD意思是“plain-old-data”(C++-98:1.8;5),它是C++中的一個重要概念。POD類型必修滿足以下條件:
將組成它的一個對象的各字節拷貝到一個字節數組中,然後再將它們重新拷貝回原先的對象所占的存儲區中;此時對象應該仍具有它原來的值。
POD類型定義:標量類型、POD結構類型、POD聯合類型,這些類型的數組,以及這些類型以 const/volatile修飾的版本。
POD結構:一個聚合體類,其任何非靜態成員的類型都不能是如下任意一種:指向成員的指針、非 POD聯合,以及以上這些類型的數組或引用,同時該聚合體類不允許包含用戶自定義的拷貝賦值操作符和用戶自定義的析夠函數。
POD類型的重要作用:POD類型允許 C++與 C交互!
template<typenameT>
structmust_be_pod
{
static void constraints()
{
union{ T T_is_not_POD_type; };
}
};
模板原理:利用 POD類型可以放在 union中實現這個約束。
使用場景舉例:
template <typenameT>
union must_be_pod
{
T t;
};
template<typenameT>
void SafeZeroMemory(T* p, size_t size)
{
must_be_pod<T>();
memset(p, 0,size);
}
class A
{
public:
~A(){}
void Reset() { _value = 0; }
private:
int _value;
};
class B
{
public:
void Reset() { _value = 0; }
private:
int _value;
};
class C
{
private:
int _value;
};
void TestImpl()
{
A a;
B b;
C c;
int i = 0;
int* p = 0;
SafeZeroMemory(&a,sizeof(a)); // 編譯失敗
SafeZeroMemory(&b,sizeof(b)); // 編譯通過
SafeZeroMemory(&c,sizeof(c)); // 編譯通過
SafeZeroMemory(&i,sizeof(i)); // 編譯通過
SafeZeroMemory(&p,sizeof(p)); // 編譯通過
};
上面例子中,類 A有析構函數,因此它不是 POD結構。
一個類或結構如果有構造函數、析構函數、復制構造函數、賦值函數、或虛函數、它從非 POD的類或結構繼承或者從多個類繼承,都不是 POD結構。
5. must_be_same_size()大小必須相同
template<typenameT>
structsize_of
{
enum{ value = sizeof(T) };
};
template<>
structsize_of<void>
{
enum{ value = 0 };
};
template<typenameT1, typename T2>
structmust_be_same_size
{
public:
~must_be_same_size()
{
void (*pfn)(void) =constraints;
(void)(pfn);
}
private:
static void constraints()
{
struct must_be_same_size_
{
int T1_must_be_same_size_as_T2 : size_of<T1>::value ==size_of<T2>::value;
};
}
};
模板原理:已命名位域不能有零寬度。
使用場景舉例:
template<typenameT1, typename T2>
void ObjCopy(T1& l, const T2&r)
{
must_be_same_size<T1,T2>();
must_be_pod<T1>();
must_be_pod<T2>();
memcpy(&l, &r,sizeof(l));
}www.2cto.com
structA { int foo; };
structB { int foo; };
structC { char foo; };
void TestImpl()
{
A a;
B b;
C c;
ObjCopy(a,b); // 編譯通過
ObjCopy(a,c); // 編譯失敗
}
上述例子中,結構 C的大小和結構 A的不同,因此編譯失敗。
作者:lzy0168