C++ -Template 的連結問題

template unresolved external (無法解析的外部符號)

先看看沒有使用 template 的樣子

範例1

test.h

#ifndef __TEST_H
#define __TEST_H

int MaxTest(int x, int y);

#endif

test.cpp

#include "test.h"
int MaxTest(int x, int y)
{
    if (x > y)
        return x;
    return y;
}

main.cpp

#include<iostream>
#include "test.h"
using namespace std;

int main()
{
    cout << MaxTest(3, 4) << endl;

    system("pause");
    return 0;
}

執行結果

改成 template

範例2

test.h

#ifndef __TEST_H
#define __TEST_H

template <class T>
T MaxTest(T x, T y);

#endif

test.cpp

#include "test.h"

template <class T>
T MaxTest(T x, T y)
{
    if (x > y)
        return x;
    return y;
}

main.cpp

#include<iostream>
#include "test.h"
using namespace std;

int main()
{
    cout << MaxTest(3, 4) << endl;

    system("pause");
    return 0;
}

連結錯誤!!!
1>main.obj : error LNK2019: unresolved external symbol “int __cdecl MaxTest(int,int)” (??$MaxTest@H@@YAHHH@Z)
1>D:\LibProject\keyproj\tests\templateTest02\Debug\templateTest02.exe : fatal error LNK1120: 1 unresolved externals

1>main.obj : error LNK2019: 在函式 “int __cdecl MaxTest(int,int)” (??$MaxTest@H@@YAHHH@Z)_main 中參考了無法解析的外部符號
1>D:\LibProject\keyproj\tests\templateTest02\Debug\templateTest02.exe : fatal error LNK1120: 1 個無法解析的外部符號

…為什麼?
Template class 也會這樣,這是為什麼呢?

首先,C++ standard 中提到,一個編譯單元(translation unit)是指一個 .cpp 以及它所 include 的所有標頭檔 .h 文件,標頭檔裡的所有程式碼會被擴展到包含它的 cpp 檔裡,然後編譯器編譯該 cpp 檔成一個 obj 檔案,obj 是擁有 PE(Portable Executable,是 windows 可執行文件)文件格式,本身是二進位碼,但是不一定能夠執行,因為不能保證裡面有 main 函式。當編譯器把各別的 cpp 檔都編譯成 obj 檔後,再由連結器(linker)連結成一個執行檔(.exe)。

範例3

test.h

#ifndef __TEST_H
#define __TEST_H

int MaxTest(int x, int y);	//這裡宣告一個函式

#endif

test.cpp

#include "test.h"
int MaxTest(int x, int y)	//這裡實作函式
{
    if (x > y)
	    return x;
    return y;
}

main.cpp

#include<iostream>
#include "test.h"
using namespace std;

int main()
{
    cout << MaxTest(3, 4) << endl;	//這裡呼叫函式

    system("pause");
    return 0;
}

在這個例子中,test.cpp 和 main.cpp 被編譯成不同的 obj 檔(test.obj, main.obj)。在 main.cpp 中使用了 MaxTest 函式,因為 main.cpp 有 include test.h,所以當編譯器編譯 main.cpp 時,它只知道這裡的 MaxTest 函式是 test.h 裡一個關於 int MaxTest(int x, int y) 的宣告,把這裡的 MaxTest 函式當作一個外部連結類型,也就是它的實現一定會在另一個 obj 檔裡。main.obj 對呼叫 MaxTest 的動作只會產生一個 call 的指令 call MaxTest 。在編譯時,這個 call 指令顯然是錯誤的,因為 main.obj 沒有 MaxTest 函式的實現程式碼。之後必須依靠連結器,連結器在其他的 obj 檔中尋找實作 MaxTest 函式的程式碼,找到之後將 main.obj 裡的 call 代換成實際的函式位址。

簡單來說,編譯 main.cpp 時,編譯器不知道 MaxTest 函式的實作程式碼在哪裡,所以遇到 MaxTest 函式時只是先給個指示,指示連結器當找到 MaxTest 實作程式碼實再來取代。編譯 test.cpp 時,編譯器找到 MaxTest 函式的實作程式碼,並編在 test.obj 裡。連結時,就把剛剛 main.obj 裡懸而未決的位址換成 MaxTest 實作程式碼的真正位址。

但是 template 比較特別,是在呼叫 template function 時,C++ 編譯器依據引數的資料型態,「自動」產生出所需的函式,這個過程稱為實例化(instantiate)。

範例4

main.cpp

#include<iostream>
using namespace std;

template<class T>
T MaxTest(T x, T y)
{
	if (x > y)
		return x;
	return y;
}

int main()
{
	MaxTest(3, 4);
	/*
	  型態是int
	  編譯器自動實例化出
	  int MaxTest(int x, int y)
	  的程式碼
	*/
	system("pause");
	return 0;
}

讓我們再重新看一次

範例2

test.h

#ifndef __TEST_H
#define __TEST_H

template <class T>
T MaxTest(T x, T y);

#endif

test.cpp

#include "test.h"

template <class T>
T MaxTest(T x, T y)
{
    if (x > y)
        return x;
    return y;
}

main.cpp

#include<iostream>
#include "test.h"
using namespace std;

int main()
{
    cout << MaxTest(3, 4) << endl;//重點在這裡

    system("pause");
    return 0;
}

編譯器在這裡並不知道 int MaxTest(int x, int y) 的實作在哪裡,因為它不在 test.h 裡,只好希望連結器可以在別的 obj 裡找到,但是 test.cpp 裡的是T MaxTest(T x, T y),而不是 int MaxTest(int x, int y),所以根本就沒有 int MaxTest(int x, int y) 的實作程式碼,因此連結錯誤。

關鍵在於 template 只有在需要時才會被實例化出來,所以當編譯器只看到 template 的宣告時,他不能實例化該 template,只能先建立一個外部連結。然而當實現該 template 的 cpp 沒有用到實例化時,編譯器就無法實例化,所以連結失敗。

要解決問題的方法如下

範例5

test.h

#ifndef __TEST_H
#define __TEST_H

template <class T>
T MaxTest(T x, T y);

#endif

test.cpp

#include "test.h"

template <class T>
T MaxTest(T x, T y)
{
    if (x > y)
        return x;
    return y;
}

//在這裡實例化
template int MaxTest<int>(int x, int y);
template float MaxTest<float>(float x, float y);

main.cpp

#include<iostream>
#include "test.h"
using namespace std;

int main()
{
    cout << MaxTest(3, 4) << endl;

    system("pause");
    return 0;
}

class 的 template 的解決方法可參考下面的網址

參考網址
http://www.cplusplus.com/forum/articles/14272/
https://stackoverflow.com/questions/115703/storing-c-template-function-definitions-in-a-cpp-file

在〈C++ -Template 的連結問題〉中有 1 則留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *