簡體   English   中英

模板函數內的靜態變量

[英]Static variable inside template function

在C ++中,如果在header.hpp中定義此函數

void incAndShow()
{
  static int myStaticVar = 0;
  std::cout << ++myStaticVar << " " << std::endl;
}

並且在至少兩個.cpp文件中包含header.hpp。 然后你將有multiple definition of incAndShow() 這是預期的。 但是,如果您向該函數添加模板

template <class T>
void incAndShow()
{
  static int myStaticVar = 0;
  std::cout << ++myStaticVar << " " << std::endl;
}

那么你就不會有任何錯誤的multiple definition of 同樣,使用相同模板調用函數的兩個不同的.cpp(例如incAndShow<int>() )將共享myStaticVar 這是正常的嗎? 我問這個問題,因為我確實依賴這個“功能”(共享靜態變量),我想確保不僅是我的實現正在這樣做。

你可以依靠這個。 ODR(一個定義規則)在標准中以3.2/5表示,其中D代表非靜態函數模板(由我提供的草書字體)

如果D是模板,並且是在多個翻譯單元中定義的,那么上面列表中的最后四個要求將應用於模板定義(14.6.3)中使用的模板封閉范圍中的名稱,以及相關名稱在實例化時(14.6.2)。 如果D的定義滿足所有這些要求,那么程序應該表現得就像D的單個定義一樣。如果D的定義不滿足這些要求,則行為是不確定的。

在最后四個要求中,最重要的兩個是粗略的

  • D的每個定義應由相同的令牌序列組成
  • 每個定義中的名稱應指相同的東西(“實體”)

編輯

我認為單憑這一點不足以保證不同實例中的靜態變量都是相同的。 以上僅保證模板的多個定義有效。 它沒有說明由它產生的特化。

這就是連接起作用的地方。如果函數模板特化(一個函數)的名稱具有外部鏈接( 3.5/4 ),那么引用這種特化的名稱引用相同的函數。 對於聲明為static的模板,從中實例化的函數具有內部鏈接,因為

從具有內部鏈接的模板生成的實體與在其他翻譯單元中生成的所有實體不同。 -- 14/4

具有命名空間范圍(3.3.6)的名稱具有內部鏈接,如果它是顯式聲明為靜態的對象,引用,函數或函數模板的名稱-- 3.5/3

如果函數模板沒有用static聲明,那么它有外部鏈接(順便說一下,這也是我們必須遵循ODR的原因。否則, D根本就不會被多次定義!)。 這可以從14/4 (連同3.5/3 )得出

非成員函數模板可以具有內部鏈接; 任何其他模板名稱都應具有外部鏈接。 -- 14/4

最后,我們得出結論,從具有外部鏈接的函數模板生成的函數模板特化本身具有3.5/4外部鏈接:

具有命名空間作用域的名稱具有外部鏈接,如果它是[...]函數的名稱,除非它具有內部鏈接-- 3.5/4

並且當它已經內部連接是通過解釋的3.5/3用於通過明確的特化所提供的功能,和14/4用於產生特化(模板實例)。 由於您的模板名稱具有外部鏈接,因此您的所有特化都具有外部鏈接:如果您使用來自不同翻譯單元的名稱( incAndShow<T> ),則它們將引用相同的函數,這意味着您的每個靜態對象都是相同的場合。

我理解你的問題。 您要問的是,模板化函數的每個版本都有自己的myStaticVar實例是否正常。 (例如: incAndShow<int> vs. intAndShow<float>答案是肯定的。

你的另一個問題是,如果兩個文件包含包含模板函數的標題,它們是否仍會共享給定T的靜態變量。我會說是的。

創建函數模板時的區別在於它具有外部鏈接。 所有翻譯單元都可以訪問相同的incAndShow。

從C ++標准工作草案N2798(2008-10-04)釋義:14第4部分:非成員函數模板可以具有內部鏈接,其他人總是具有外部鏈接。 14.8第2點:每個專業化都有自己的靜態變量副本。

除非在未命名的命名空間中聲明它,否則您的函數模板應具有外部鏈接。 因此,對於與函數模板一起使用的每個T,您應該獲得一個用於吞吐程序的靜態變量。 換句話說,可以依賴程序中每個實例化只有一個靜態變量(一個用於T == int,一個用於T == short等)。

順便說一句,如果您在不同的翻譯單元中以不同方式定義incAndShow,這可能會導致奇怪的情況。 例如,如果您將其定義為在一個文件中遞增而在另一個文件中遞減(不通過將函數放入未命名的命名空間來指定內部鏈接)兩者將最終共享相同的函數,這將在編譯時有效地隨機選擇(使用g ++,它取決於命令行上給定目標文件的順序)。

模板根據需要進行實例化,這意味着編譯器(在這種情況下鏈接器?)將確保您不會得到同一模板的多個實例以及您需要的那些模板實例 - 在您的case only incAndShow<int>()是實例化的,沒有別的(否則編譯器必須嘗試為每個沒有意義的類型實例化)。

所以我假設它用來確定實例化模板的類型的相同方法阻止它為同一類型實例化兩次,例如只有一個incAndShow<int>()實例

這與非模板代碼不同。

是的,它是“正常的”,但無論你試圖通過這個“功能”實現什么可能都值得懷疑。 嘗試解釋為什么要使用本地靜態變量,可能我們可以想出一個更干凈的方法來做到這一點。

這是正常的原因是因為編譯和鏈接模板函數的方式。 每個翻譯單元(在您的情況下為兩個.cpp)都可以看到他們自己的incAndShow副本,當程序鏈接在一起時,兩個incAndShow將合並為一個。 如果在頭文件中內聯聲明常規函數,則會產生類似的效果。

以此示例顯示行為是絕對期望的:

#include <iostream>

template <class T> class Some
{
public:
   static int stat;
};

template<class T>
int Some<T>::stat = 10;

void main()
{
   Some<int>::stat = 5;
   std::cout << Some<int>::stat   << std::endl;
   std::cout << Some<char>::stat  << std::endl;
   std::cout << Some<float>::stat << std::endl;
   std::cout << Some<long>::stat  << std::endl;
}

你得到: 5 10 10 10 10

上面顯示靜態變量的變化僅適用於“int”類型,因此在您的情況下您沒有看到任何問題。

  • 模板只有在實例化(即使用)后才會實際轉換為代碼
  • 標頭不能用於實現代碼,但僅用於聲明

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM