標籤:

泛型和反射都必須依賴運行時嗎?

C++中有模版,這個應該和Java/C#的泛型基本是差不多的功能吧,但聽說C#的泛型更進一步,具體表現在哪些方面?C#這種「更進一步」的泛型是否必須依賴CLR;在C++中有可能實現反射嗎,反射是否必須依賴Runtime支持?


參數多態大體上就是多了一個大 λ

如果編譯的時候能完全確定這些大 λ 傳入的是什麼,就可以消除掉運行時開銷,比如 HM 體制的 OCaml

否則就必須引入運行時類型,(開了 GHC 擴展的)Haskell 就大量使用了運行時的類型信息


大的方面來說:

Java的泛型是 type erasure, 所有類型信息在運行時都丟失了,所以java 范型主要能做的就是帶來了 type safety, 僅此而已。

C# 的泛型是 type reification, reification的過程簡單來說就是把一個abstract的東西根據定義變成一個concrete的東西,比如根據List& 和 int, 生成 List&。所有范型的定義會維護在運行時的內存中。因為類型信息一直保持著,除了有了type safety外,所有相關工具鏈都可以獲取type parameter的信息,帶來的好處有例如使用primitive類型時不需要boxing等。

C++ template 的泛型則完全是編譯期的事情,不用對運行時帶來任何額外開銷,弊端就是二進位代碼膨脹。

個人認為,這些都是各種語言根據歷史局限對各種范型實現技術的取捨,說白了,還是tradeoff。有個八卦好像是說當年 java 1.5時增加范型支持之所以選擇 type erasure 是因為不想導致需要改變現有的.class文件的format,後來發現因為別的原因還是需要對.class format做修改。。。

有興趣的可以詳細看看以下兩個鏈接:

Comparing Java and C# Generics - Jonathan Pryors web log

What is reification?


題主說的都是錯的。

C++的模板並不是和Java/C#的泛型基本是差不多的功能。

聽說C#的泛型也沒有更進一步。

泛型和反射都可以是編譯期行為,不需要依賴運行時。


C++當然可以有反射,我在 GacUI ( https://github.com/vczh-libraries ) 就做過。原理很簡單,如果你使用GacUI的時候顯式聲明了要反射,那編譯完你的exe就會多出10M,我把反射需要的代碼都給你添加進去了。然後你就可以

  • 用字元串new類
  • 用字元串調用函數、屬性、成員變數和事件
  • 動態創建一個類多重繼承自C++的幾個允許被你動態繼承的類,還能互相cast,delete的時候等於一起delete

題主可以把玩把玩。

=================

C#的更進一步表現在虛函數也可以是模板的,然後反射又可以創建沒有出現過的實例化組合,所以C#的這個對runtime的要求是肯定的。其他語言都可以不需要runtime。


1.JAVA編譯和運行相對獨立,java編譯止步於生成*.class文件。後續運行時由虛擬機提供,所以就有了JAVA的口號一次編譯多處運行。因為*.class是與平台無關的,很可能在A機器上編譯的*.class放到B機器上的虛擬機運行,這就要求虛擬機知道它該知道的關於源碼的所有信息,而這些信息以非常格式化的形式文檔化,詳細可參見The Java? Language Specification:

ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}

基本上所有的信息都能存在於這個中間表示中,所以java才有那麼強大的反射能力,可以知道方法名,欄位名,介面名,棧幀布局...唔..自己看看jvm specification吧

2. C++不能實現像java一樣強大的反射。C++編譯鏈接得到的是與平台相關的程序,所以你不能把在Windows*上編譯鏈接生成的程序放到Linux上運行。上述這些信息在C++ release中是tan90°的,很多信息都在編譯期抹去了,比如你在運行時不可能知道成員變數的名字,而且每個編譯器關於類的內存布局和name mangling也不相同,沒有一個規範,寫不出通用反射庫。

3.C++的模板和JAVA的泛型差遠了,差多少?差半個太陽系

4.C#不了解


泛型 不一定需要, C++/D 都有,不需要以來運行時的、、

反射 : 靜態反射不需要: C++ / D

動態反射需要: C#/JAVA/go


看起來題主可能對運行時有些誤解。

整個c#代碼將被編譯成il(中間語言)。等程序運行的時候,由運行時負責解釋執行il。

所以我感覺題主像是在問:電視換台必須用電嗎?

答案是:是的,不止換台,整個電視運行都是用電的。


C#的泛型不了解,單說反射。

我們來探索下維基百科。根據詞條Reflection (computer programming),反射特指程序運行時獲取或修改自身程序結構的能力。有趣的是,詞條Compile-time reflection直接被重定向到了前面的Reflection (computer programming)。而根據詞條Metaprogramming - Wikipedia,如果一個語言的元編程語言是自身,那麼這種能力就叫反射。

所以狹義地說,反射就是運行時的東西。至於「編譯期反射」,如果硬要說有的話,我覺得宏和模板算是低配版的反射了吧。現在C++不是已經有「模板元編程」的說法了嗎?如果C++模板語法也算C++語言的一部分的話,那這就是反射了。

其實運行時反射的很大一部分基礎,就是保留到運行時的一些編譯期的信息。所以我的結論就是,反射並不是一定要依賴運行時,恰恰相反,反射非常依賴編譯時。


就C++來說,泛型主要還是依靠模板和類型推導,所以我認為它是不依賴於運行時的。

如果C++需要支持反射,我的理解還是需要運行時支持,因為C++的typeof和dynamic_cast是需要運行庫支持的,所以至少在這一塊是繞不開的。可能的實現是為每個類型生成meta data數據用於存放反射信息(比如根據名字得到類函數指針等),另外還需要為反射的實現生成相關的代碼。

如果真有興趣,完全可以利用libClang實現一個基於clang front-end的代碼生成工具,用以生成每個類的支持反射的代碼。


只能說上述的回答都只是對java的泛型而言是正確,對於C#而言,泛型是需要運行時支持的。

由於java的泛型是編譯期行為,泛型利用類型擦除實現,所以在java中以下語法是錯誤的

T t =new T();

而在C#下則可行,這一點確實是C#更進一步的表現。


我說3點。


1 就 泛型和反射 的基本思想來說,這倆功能的實現並不依賴於運行時。

例如C++而言,本身就支持泛型,如果需要支持反射,只需要添加相應的管理功能即可。

2 依賴運行時的語言 支持 泛型和反射 技術需要依賴運行時是因為其任何代碼的執行都需要運行時。

例如你說的Java/C#是一次編譯成中間語言,然後在設備中在進一步的編譯/解釋。其任何代碼都在後一步需要runtime支持。

3 既然有了runtime支持,這些語言的 泛型和反射 技術自然可以多一些在沒有運行時搞不定的能力

例如動態的(類or方法)調用的確認,或者說重載,重寫。

建議題主你通過這條邏輯來理解。多聽聽其他回答中大佬們的話。(⊙o⊙)…


這些東西需要運行時的唯一原因是,所需信息只能在運行時獲取。

C++的模板並不是泛型,因為你沒辦法直接把模板編譯成obj文件之後將obj文件和頭文件單獨發布出去給別人用:無論如何,在編譯的時候都必須知道模板的具體實例,就像靜態數組那樣。

Java的泛型是編譯期語法糖,運行期會變成基類類型。於是相比C#,泛型lambda函數的使用有限制。甚至,從某種意義上來說,這根本就不是泛型,而只是基類型+強制實施的類型規範。

C#的泛型倒是真的泛型了,你完全可以用泛型寫一整個裝配件而且只發布那個裝配件,運行時貌似也可以反射到實例類型。

反射的話,有一種定義是,元編程中,源語言和目標語言相同。這種情況當然也可以是編譯期的。但是,即使是這種情況,也是要運行元編譯器的時候才能讀取到源源代碼(好拗口)中的類型,而不能直接寫死在元編譯器中,因此,也算是另一種運行時了。

目前我們用的反射,其實可以理解為在解釋器前面再套一個元解釋器的套路。當然,這個元解釋器也可以分成兩部分,一個對象模型parser和一個使用對象模型來做反射操作的真·解釋器。(舉個例子,用JS操作HTML的DOM,但是由於源語言和目標語言不同,這種情況並不是反射,只是單純的元編程)


不一定,許多語言對泛型的支持是因為編譯器對代碼進行了轉換,例如相同名字,不同參數類型的函數定義,在編譯期會轉換成不同名字的函數。

類型推斷系統,也能在編譯期獲取許多類型信息。

但其他的,例如在代碼中出現的獲取動態變數類型的代碼,就只能在運行時處理,當然運行時是否支持這種反射,就不一定都支持了。


C++的泛型是編譯期的


推薦閱讀:

offer比較:C++職位和golang職位?
請問在c++11之前,有什麼方法可以實現可變參的函數模板呢?
現代C++核心指導中提到的 span<T> 類型到底是哪個?
C++ 為什麼有時候必須額外寫 template?
C++ 模版元編程對快速開發原型有何幫助?

TAG:編程語言 | C | C# |