搜尋

首頁  >  問答  >  主體

c++的模板问题

ArrayList.h

#pragma once
template <class T>
class ArrayList
{
public:
    ArrayList(int size);
    ~ArrayList(void);

private:
    T* arrayList;
    int maxSize;
};

ArrayList.cpp

#include "ArrayList.h"

template <class T>
 ArrayList<T>::ArrayList(const int size)
{
    maxSize = size;
    arrayList = new T[maxSize];
}

 template <class T>
 ArrayList<T>::~ArrayList(void)
{
    delete [] arrayList;
}

源.cpp

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

int main()
{
    ArrayList<int> list(2);
    system("pause");
    return 0;
}

出现的错误是

将三个文件写到一个文件里后就没有错误了

#include <iostream>
using namespace std;

template <class T>
class ArrayList
{
public:
    ArrayList(int size);
    ~ArrayList(void);

private:
    T* arrayList;
    int maxSize;
};

template <class T>
 ArrayList<T>::ArrayList(int size)
{
    maxSize = size;
    arrayList = new T[maxSize];
}

 template <class T>
 ArrayList<T>::~ArrayList(void)
{
    delete [] arrayList;
}

 int main()
{
    ArrayList<int> list(2);
    system("pause");
    return 0;
}

请问这是为什么

高洛峰高洛峰2803 天前445

全部回覆(5)我來回復

  • 怪我咯

    怪我咯2017-04-17 13:06:54

    簡單答案是「模板要寫在.h檔裡」。
    但我想這不是題主想要的。

    要搞清楚這問題,你得先明白分開.h和.cpp的意義。

    平常我們分開.cpp和.h,為的並不是模組化。
    模組化本身體現在“把不同的東西分別放在不同的文件”,但.cpp和.h卻是“把相同的東西分別放在兩個文件”。
    事實上,你大可以完全不寫.h檔,只寫.cpp,然後#include "bla.cpp",一樣可以透過編譯。
    (只要.cpp裡的函數/變數/類別等的名字沒撞車…)

    分開.h和.cpp的最大原因,在於把各.cpp分開編譯。

    在建構程式時,我們把所有.cpp(原始碼檔)編譯成.o(目標檔),然後把.o整合成執行檔(或函式庫)。
    假設我們的軟體專案裡,有100個.cpp文件,而在上次建構之後,我們只改了其中一個。
    那麼最好的情況是只有一個.o文件需要重新編譯,其餘99個可以直接用來整合可執行文件,省下編譯時間。
    這對建構大型軟體來說很有用,總不能你改了Office的一行程式碼,就把數十GB的源碼都重新編譯吧…

    然後我們有另一個問題:
    .cpp全部都分開了,互相不知道彼此的存在,那如果a.cpp要用到b.cpp裡的東西,怎麼辦?
    a.cpp需要知道b.cpp提供了甚麼功能,但不需要知道那些功能是怎麼實現的。
    而「展示所提供的功能」就是.h檔案的作用:我提供了名為BlaBlaBla的類,裡面有某某變數某某方法,還有個叫做doSomething的函數,它接受兩個int作為參數,…
    於是所有.cpp只需要引入.h,看看能做些什麼甚麼就行了。
    它們不需要管.h裡說的那些功能是怎樣實現的,反正只要知道最終整合為可執行文件事,這些事都能完成,就夠了。
    這就實現了依賴分離,從而讓各.cpp可以分開編譯。

    但模板不同:模板沒辦法分開編譯。
    為甚麼?看看以下的範例:

    template <typename T>
    T* makeArray(size_t n) {
      return new T[n];
    }
    

    編譯器看到了這行程式碼,它應該做甚麼?
    一般情況下,假如編譯器看到一個類似的東西:

    SomeClass* makeArray(size_t n) {
      return new SomeClass[n];
    }
    

    它應該在.o裡寫下類似這樣的二進位代碼:

    分配一段足够长的空间(n * sizeof(SomeClass))
    找到SomeClass对应的构造函数
    对空间里的每一个sizeof(SomeClass)大小的位置,执行一遍SomeClass的构造函数
    返回空间的地址
    

    但在模板的例子裡,它辦不到:它根本不知道T是甚鬼,sizeof(T)不明,無法編譯!

    另一個問題在於,模板有些特性,讓你不到實際知道T之前,根本不知道編譯出來的程式碼長甚麼樣子。
    其中一個叫做偏特化:

    template<typename T>
    int getValue(T t) {
        return t.value;
    }
    
    template<>
    int getValue(int t) {
        return 0;
    }
    

    這東西怎麼編譯?在不知道T是甚麼的情況下,你根本不知道該用上面的版本還是下面的版本…

    因此,模板程式碼無法被編譯成.o檔。
    其他.cpp要使用這段模板,只有直接把它複製貼上到自己的程式碼裡,也就是#include幹的事。
    事實上,這也體驗了「模板」這個名字:我只是個原始碼的模板而已,是用來方便你複製貼上然後修改的,不是用來編譯的!

    回覆
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-17 13:06:54

    那個報錯是因為模板無法實例化。類別模板的聲明及其成員函數的定義要放在同一個文件,即放在.h文件中。

    回覆
    0
  • 黄舟

    黄舟2017-04-17 13:06:54

    我來告訴題主你終極的答案。

    其實這個模板寫cpp裡面的事情早就有人propose了。世界上有一個最屌的公司叫EDG,負責寫正確的C++編譯器,然後告訴標準委員會他們的看法,順便賣一下他們的C++/Java/Fortran的編譯器前端。他們花了兩年終於把這件事給做出來了,裡麵包括如何把模板程式碼的AST序列化進obj/lib以便於在連結的時候再展開重新編譯一邊等超級複雜的工作。最後得出一個結論:

    這麼屌的功能,只有我們知道怎麼做,你們是做不出來的。

    所以全世界就作罷了,再也不提這件事情。

    因此其它的答案說了一大堆道理其實都是不正確的,因為並沒有什麼理由阻止C++編譯器在鏈接的時候才編譯模板的源代碼,這全部都是工程上的取捨。做這件事情,好處又幾乎沒有,但是編譯器的複雜度又上升了一大截。所以沒有什麼意義。

    回覆
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-17 13:06:54

    如果不用#pragma once 改成用這個試試看呢

    #ifndef ARRAY_LIST_H
    #define ARRAY_LIST_H
    
    
    #endif

    回覆
    0
  • 大家讲道理

    大家讲道理2017-04-17 13:06:54

    實例化模板類別及函數時需要「看到」相關實現,不然編譯器怎麼特例化這部分程式碼呀?

    回覆
    0
  • 取消回覆