2012年4月21日 星期六

C++11 導入的新觀念 extern template

雖然說是 C++11 的新觀念, 但早在 C++0x 時代 (C++11 還沒正式確定前的草案) 各家 Compiler 就已經在追 spec 了. 所以目前 Visual C++ 2008/2010 以及 GNU G++ 4.3 版以後都已經有支援 extern template.

提到 extern template 之前, 先複習一下 Template: C++ 的 Template 是一種樣版的觀念, 定義 Template 時通常會定義一些尚未確定的變數型態. 所以當編譯器處理到 Template 定義時, 它並不能直接生成執行碼, 而是要等到有人使用這個 Template, 決定了那些未定型態的變數真正的型態之後 (instantiate, 或稱實例化) , 才能生成執行碼. 這也是為何有些高度使用 Template 的 C++ 的函式庫能夠號稱以 "header only" 的型式散佈 (Ex: Boost C++ Library).

不過這樣作法會導致一個問題, 若有一個 Template (Ex: std::vector<T>), 在很多不同的檔案中都被使用到了, 而且以相同的對象實例化 (Ex: 皆為std::vector<int>), 那麼編譯器依序編譯這些檔案時, 每份 obj 文件中都會有一份該 Template 針對該型態實例化後的代碼 (*1). 這樣不但費時而且多餘. extern template 所要解決的就是這個問題, 它可以用來向編譯器宣告: 某個 Template 的特定對象實例化代碼, 可以在別處找到, 請不要再翻一次. 最後在連結階段再去尋找它的實例化代碼並且連結起來, 以達到加速編譯的目的 (最終程式大小也會變小).

為了驗證這個觀念, 做了一個小實驗. 有一個 Template class 名為 MyClass<T>, 定義在 MyClass.h:
#pragma once
#include <stdio.h>

template<typename T>
class MyClass
{
public:
    void print(T data) { printf(SALT ": data %d\n", data); }
};

假設某個函式庫使用到了這個 MyClass<T>:
#define SALT "lib"
#include "MyClass.h"

// libfunc() is not used in main program.
// It is here just to make sure compiler 
// instantiate the MyClass<int>
extern "C" void libfunc(int data) {
    MyClass<int> b;
    b.print(data);
}

// Compile this library by:
// g++ -o libtest.so thisfile.cpp -fpic -shared

另外使用這個函式庫的主程式也引用了 MyClass.h:
#define SALT "main"
#include "MyClass.h"

extern template class MyClass<int>;

int main(int argc, char *argv[]) {
    MyClass<int> b;
    b.print(50);
    return 0;
}

// Compile this by:
// g++ -o test thisfile.cpp -L. -ltest
// Run by:
// env LD_LIBRARY_PATH=. ./test

因為兩份文件定義了不同的 SALT, 我們可以用此鑑別最終呼叫的究竟是哪一方的 MyClass 實例化代碼.

若沒有 extern template class MyClass<int>; 這一行, 編譯器會幫主程式也產生一份 MyClass<int> 的實例化代碼, 輸出會是:
main: data 50

反之若有 extern template class 那一行, 編譯器就不會再實例化 MyClass<int>, 最後連結時發現函式庫中已有所需的代碼, 就會直接使用, 因此輸出會是:
lib: data 50

這樣的作法還提供了跨函式庫傳遞樣版物件時一個很好的解套方式, 回憶跨函式庫進行記憶體配置與釋放需考慮 Runtime 連結方式, 連結靜態 Runtime 時, 因為分立的 Heap 導致不能相互釋放對方配置的記憶體區塊, 使得樣版設計時要考慮更多, 綁手綁腳. 現在使用方只要確定有宣告 extern template, 確定使用函式庫內的樣版實例化代碼就可以保證連結到同樣的 Runtime 使用同樣的 Heap. 避掉這個棘手的情況.

*1: http://en.wikipedia.org/wiki/C%2B%2B0x#Extern_template

沒有留言:

張貼留言