2010-07-16 26 views
4

此問題是在函數實例化的外部文件中定義的名稱空間中定義和聲明函數模板。這是我能想到的最小的可重複的例子。 4個文件如下:單獨文件中名稱空間中的函數模板編譯得很好,但鏈接器找不到它

在一個名爲命名空間中的函數模板聲明:

// bar.h 
#include <algorithm> 

namespace barspace { 

    template <typename Iter> 
    void DoSomething (Iter first, Iter last); 

} 

在一個單獨的文件中的函數模板定義:

// bar.cpp 
#include "bar.h" 

namespace barspace { 

    template <typename Iter> 
    void DoSomething (Iter first, Iter last) { 
     typedef typename std::iterator_traits<Iter>::value_type val_t; 
     std::sort (first, last); 
    } 

} // namespace barspace 

主程序

// foo.h 
#include "bar.h" 
#include <vector> 

最後,調用函數模板的主程序:

//foo.cpp 
#include "foo.h" 

int main() { 

    std::vector<double> v_d; 
    for (int i = 0; i < 10; i++) { 
    v_d.push_back (i); 
    } 

    barspace::DoSomething (v_d.begin(), v_d.end()); 

    return 0; 
} 

我編譯如下:

g++ -c -o bar.o bar.cpp

g++ -c -o foo.o foo.cpp

這些運行正常。現在對於鏈接:

foo.o: In function `main': 
foo.cpp:(.text+0x6e): undefined reference to `void barspace::DoSomething<__gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > > >(__gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >, __gnu_cxx::__normal_iterator<double*, std::vector<double, std::allocator<double> > >)' 
collect2: ld returned 1 exit status 

有一個明顯的問題,代碼沒有得到namespace內提供來自或在:

g++ bar.o foo.o -o foobar

約在不確定的參考產生的編譯器錯誤bar編譯單元。另外,當我試圖將DoSomething的定義放在bar.h頭文件中時,正如我想在單獨的.cpp文件中定義類模板方法時所想到的那樣,我會得到相同的錯誤。

你能否介紹一下我的編譯器鏈接錯誤?

回答

7

您試圖將模板函數的實現隱藏到cpp文件中,對於大多數編譯器來說,這是不可能的。模板化函數/類在使用時被實例化,因此在調用DoSomething時,編譯器需要定義該函數才能編譯它。

有幾個解決方案。

  1. 將函數體移入頭文件。之前你曾經遇到過麻煩,但我會說它與其他事情有關。這是首選的方法。

  2. 包括從foo.cpp cpp文件。 (野生,但不是不常見)。

  3. 實例化模板double

// bar.cpp 
#include "bar.h" 

namespace barspace { 

    template<> 
    void DoSomething<double> (double first, double last) { 
     typedef typename std::iterator_traits<double>::value_type val_t; 
     std::sort (first, last); 
    } 

} // namespace barspace 
1

把模板定義放在一個單獨的源文件中不被很好的支持(我相信唯一支持這個的編譯器是Comeau)。

您需要定義移到頭文件:

// bar.h 

namespace barspace { 

    template <typename Iter> 
    void DoSomething (Iter first, Iter last) { 
     typedef typename std::iterator_traits<Iter>::value_type val_t; 
     std::sort (first, last); 
    } 

} // namespace barspace 
1

許多編譯器不喜歡在單獨的文件中定義的模板函數。把整個定義放在頭文件中,它應該編譯得很好。

1

必須在標題定義模板功能。您不能聲明它們,然後在實現文件中定義它們;那是行不通的。原因在於編譯器必須先將實例化爲模板,然後才能調用它們。實例化一個函數模板編譯器需要兩兩件事:

  1. 模板定義
  2. 模板參數與

實例在你的情況,編譯器的工作原理有兩個編譯單元。它可能在兩個不同的過程中運行它們,所以編譯單元彼此獨立。當編譯器編譯bar.cpp時,它會看到一個沒有請求(調用)的特定實例的模板定義。因此,編譯器不會實例化模板;實際上,編譯bar.cpp產生的結果是什麼都沒有。編譯器是「聰明的」,足以看到你不需要該模板,並將其優化到零開銷。

當然,你需要一個模板 - 在Foo.cpp中 - 但是這是一個不同的編譯單元,和現在的編譯器,忘卻了所有(或還沒有學會)約bar.cpp。然而,編譯器確實看到了模板函數的聲明,並且它做出了通常的假設:如果它被聲明瞭,那麼它在別處被定義(實例化) - 並且什麼都沒說。

最後,鏈接器走來,並得到最終的鳥瞰圖。它可以看到沒有DoSomething<Iter>(Iter, Iter)std::vector<double>::iterator實例化並抱怨。

有你的問題的一些解決方案。最好的解決方案是在聲明模板時使用export關鍵字。不幸的是,這也是最糟糕的解決方案,因爲絕大多數編譯器不支持這個標準特性。

雖然嚴重,最好的辦法是在頭文件來定義您的模板。不要在那裏申報,在那裏定義。如果原來是這樣,你將不需要bar.cpp。

相關問題