跳轉到

1993:樣板與例外處理問世——泛型與錯誤傳播革命#

事件#

1993 年前後(Cfront 3.0 與《Annotated C++ Reference Manual》ARM 時代),C++ 加入了三大特性:樣板(templates)、例外處理(exceptions) 與 命名空間(namespaces)。這三者後來全部納入 1998 年的 C++98 標準。

關鍵語法/特性#

  • 樣板(template):
  • 函式樣板:template<class T> T max(T a, T b);
  • 類別樣板:template<class T> class vector { ... };
  • 樣板特化與偏特化:為特定型別提供不同實作。
  • 樣板參數:可以是型別、整數值、甚至(C++11 之後)樣板。
  • 例外處理:try/catch/throw——錯誤沿呼叫堆疊自動向上傳播,並自動呼叫解構子(堆疊輾轉,stack unwinding)。
  • 命名空間:namespace std { ... }——大型專案的命名衝突解方。
  • 執行時期型別辨識(RTTI):dynamic_cast 與 typeid。

為何重要#

  • 樣板是 C++ 命運的轉捩點:它讓 C++ 擁有了編譯期多型(與 virtual 的執行時期多型互補),並催生了泛型程式設計(1994 STL)與後來的樣板元程式設計(TMP)。
  • 例外處理:讓錯誤處理與正常流程分離,解構子自動釋放資源——與 RAII 結合成為 C++ 的資源管理基石。
  • 命名空間:從 std:: 開始,大型函式庫不再互相衝突。

理論與實用原因#

  • 理論原因:樣板源自 Clu 的 generics 與 Ada generics 的研究,但 Stroustrup 選擇了編譯期展開(monomorphization)路線——零成本抽象:不用樣板就不付費,用了與手寫程式碼一樣快。例外處理理論則源自 Goodenough(1975)與 ML 的異常機制。
  • 實用原因:容器與演算法在 C 只能用 void* + 宏(不安全)或複製貼上(難維護)實作;錯誤碼在大型專案中常被忽略——例外強制處理。

彌補了什麼缺陷#

樣板彌補了 C++ 早期「容器只能存放特定型別或 void*、無型別安全的泛型」的缺陷;例外彌補了「錯誤碼易被忽略、資源洩漏」的缺陷;命名空間彌補了「全域命名空間衝突」的缺陷。

程式範例#

(a) 樣板——對比 C 的 void* 容器與型別安全的類別樣板:

/* C 的舊寫法:void* 容器——不安全,取出時要轉型,轉錯了編譯器不會擋 */
void* stack_top(struct stack *s);
void  stack_push(struct stack *s, void* item);

int x = *(int*)stack_top(s);      // 忘了 (int*) 或元素其實是 double?編譯照過,執行期才爆炸
// 1993 的新寫法:類別樣板——型別錯誤在編譯期就被抓到
template<class T>
class Stack {
    T data[100];
    int n = 0;
public:
    void push(const T& item) { data[n++] = item; }
    T top() const { return data[n - 1]; }
};

Stack<int>  si;
Stack<double> sd;
si.push(42);
int x = si.top();        // 不需轉型,型別安全
// si.push(3.14);        // 編譯錯誤:double 不能放進 Stack<int>

(b) 例外處理——對比 C 錯誤碼易被忽略 vs 例外強制傳播 + 解構子自動釋放資源:

/* C 的舊寫法:錯誤碼要一層層檢查,忘了檢查就静默失敗,中途 return 還會洩漏記憶體 */
char* buf = malloc(1024);
FILE* f = fopen("data.txt", "r");
if (f == NULL) { free(buf); return -1; }   // 每個出口都要記得 free,漏一個就洩漏
// 1993 的新寫法:throw 沿呼叫堆疊自動向上傳播,解構子自動釋放資源(RAII)
struct File {
    FILE* f;
    File(const char* name) : f(fopen(name, "r")) {
        if (!f) throw std::runtime_error("cannot open file");  // 錯誤無法被忽略
    }
    ~File() { if (f) fclose(f); }      // 不論從哪個出口離開,解構子保證執行
};

void read_data() {
    File in("data.txt");               // 若 throw,函式直接離開,不需清理程式碼
    // ... 使用 in.f ...
}                                      // stack unwinding 自動關閉檔案

(c) 命名空間——解決大型專案的全域命名衝突:

// 舊寫法:兩個函式庫都想叫 vector / list,只能改名或加前綴
// 新寫法:namespace 隔離,各自安然共存
namespace math_lib {
    int abs(int x) { return x < 0 ? -x : x; }
}

namespace graphics_lib {
    int abs(int x) { return x; }      // 同名也不衝突
}

int main() {
    math_lib::abs(-5);                // 用完整限定名明確指定
    graphics_lib::abs(-5);
    return 0;
}

相關條目#