提到 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
沒有留言:
張貼留言