Static assertions outside of functions help us design by constract
最近在讀《Imperfect C++ / Practical Solutions for Real-Life Programming》這本書,是 Matthew Wilson 寫的,有常在讀 C/C++ Users Journal 的人,應該對他不陌生。不過,開始上班後,自己可以用來讀書的大塊時間變少了,所以到現在才剛把第一章唸完。
第一章主要是在講 Design by Contract,在 C++ 裡,其所需之相關技術,最重要的大概就屬 static assertion 與 type traits 了。要實作這兩種技術,最好的 reference design 當屬 boost 裡的 static_assert 與 type_traits 這兩個 libraries。
由於不方便直接採用 boost,故必須自己實作一個來用,而我一直到了最近幾天,才真的開始動手。遲到最近的主要原因是因為,type traits 真的很難寫,必須對各個 compiler 的特性與行為,有深入地瞭解才行。有興趣的人,可以去稍微 trace 一下 boost 是怎麼實作 type_traits,便可知其難度。
這本書的第一章裡,最讓我覺得值得一提的是,最後面的最後一段話:
It's worth noting that both the invalid index and the bit field forms have the advantage that they may be used outside of functions, whereas the switch form (and runtime assertions) may not.
是啊!static assertion 的實作法千百種,當然可以分成可以在 function 外面使用的,以及只可以在 function 內使用的的這兩種。
這有什麼差別呢?好比說我有一個 template class,限定 template parameter T 必須繼承自某 base class Base。如果 static assertion 只能在 function 裡面用,那這個 static assertion 通常會寫在 constructor 裡,當 static assertion fail 時,library user 便會被 compiler 的 error message 指引到該 constructor 裡面,此時,會不容易知道,這個錯誤是屬於整個 class 的使用,而會誤會是誤用了該 constructor。
template <class T>class Foo{public: Foo() { STATIC_ASSERT(is_derived_from<T, Base>::value); }};
反之,若 static assertion 可以寫在 function 外的話,則當 static assertion fail 時,library user 可以很明顯地發覺,他違反了 Foo class 的 design contract。
template <class T>class Foo{STATIC_ASSERT(is_derived_from<T, Base>::value);public: Foo() {}};
因此,如果 static assertion 可以在 function 外使用的話,我們便可以更直接地支援 design by contract 的概念,將 STATIC_ASSERT 另外用 #define 另外命名一個 alias 為 CLASS_CONSTRACT 如下[1]:
#define CLASS_CONSTRACT(x) STATIC_ASSERT(x)template <class T>class Foo{CLASS_CONSTRACT(is_derived_from<T, Base>::value);public: Foo() {}};
這樣子的話,design by contract 的意涵便更為直接明顯,除了可以在 static assertion fail 時,提醒 library user,更可以直接把 code 當作 document,讓程式的可讀性更佳。
- 考慮到 static assertion fail 時的 compile error message,單純用
#define將STATIC_ASSERT給 alias 成CLASS_CONSTRACT其實並不好,因為這樣的 compile error message 就講得不夠清楚對題了。 ↩



Post a Comment