背景
POD的全稱是Plain Old Data,這個Old就是體現了C語言的兼容。POD數據類型就是兼容性很重要的體現,特別是對用戶自定義的類型(struct,class定義的類)。因爲標準要求POD類型的內存佈局是完全與C語言。
原理
C++是C的超集,加入了更多語法層面的新機制,編譯器充當了一個翻譯的角色
struct POD
{
int v;
int a;
};
struct NotPOD
{
public:
NotPOD(){}
int v;
int a;
};
上面的NotPOD結構體,表面上看只是比結構體POD多定義了一個構造函數。但當編譯器將其翻譯成彙編時,對NotPOD的構造函數,是產生了額外的代碼(<< 深入探索C++對象模型 >>中有深入講解),所以它的內存佈局肯定跟POD結構體就不一樣了。最典型的虛函數,是編譯器產生一個VTABLE去實現,所以有虛函數的結構體必然不會跟C語言的內存佈局一致。
所以在C++標準中定義的POD類型標準,其實就是編譯器不會生成額外代碼的準則。
POD類型
每個版本標準的POD類型都不一樣,這也是C++ 的繁瑣之處,作爲C語言的超集,揹負了兼容C語言的包袱,每次版本更新,這種兼容性語法必然會更新。其實作爲一個開發者,應該不需要,也不應該去關注這些細節。這也是C++被詬病的地方。
POD類型的語法實在讓人覺得繁瑣,枯燥。這裏介紹C++ 11中爲POD類型(C++ 11標準是C++ 走向現代語言的一個重要標識)提供幾個檢測的模板類,再掌握幾個簡單經驗,則足以寫出 POD類型。
類型判斷工具
- template < typename T> struct std:: is_ pod;
這個工具直接判斷類型T是否是POD類型,最常用,最有用
示例代碼
#include <iostream>
struct D3
{
D3()
{
a = 1;
b = 1;
c = 1;
d = 1;
}
int a;
int b;
int c;
int d;
};
struct D4
{
int v;
int a;
};
int main()
{
std::cout << std::is_pod<D3>::value << std::endl;
std::cout << std::is_pod<D4>::value << std::endl;
}
- template < typename T> struct std:: is_ trivial;
判斷是否平凡類型,這是C++ 11中對POD類型引入的一個新的概念,滿足了trivial類型,纔是POD類型
#include <iostream>
struct D3
{
D3()
{
a = 1;
b = 1;
c = 1;
d = 1;
}
int a;
int b;
int c;
int d;
};
struct D4
{
int v;
int a;
};
int main()
{
std::cout << std::is_trivial<D3>::value << std::endl;
std::cout << std::is_trivial<D4>::value << std::endl;
}
幾個經驗
- C++中所有的基礎類型(int,char,double,指針等類型)都是POD類型
- 基礎類型的數組都是POD類型
- STL中的array爲POD類型,其餘的STL容器都是非POD類型
- 以struct來定義自定義類型。
- 不要定義構造,析構函數,複製構造函數,賦值函數,移動構造函數
- 不要定義類非靜態方法
- 類的成員不要出現非POD類型
- 不要使用繼承(隨意在特定條件下,有繼承的類型可以POD類型,但是建議還是不要使用,很容易出錯)
綜上幾點,其實最簡單,兼容最強(不同的編譯器的內存佈局的處理方法可能會不一樣)的就是以C語言struct的方式使用struct(只有基本數據類型)。
一個比較典型的例子
#include <iostaream>
struct sTest
{
std::string str;
int v;
}
int main()
{
sTest t;
memset(&t,0,sizeof(sTest));
}
上面的代碼,sTest不是POD類型,因爲string不是POD類型。直接通過memset去初始化t對象,是會崩潰的。這是個很容易犯錯誤的地方,有些程序員,覺得STL中的string比C語言的char方便,所以在結構體使用string,然後還是以memset初始化。
在C++ 中,非POD類型是不能使用memset和memcpy函數的
POD類型的用處
- 內存佈局跟C語言一樣,用於C 庫的接口。
- C++ 開發的庫,對外提供的數據類型都應該是POD類型。尤其是用於C語言或.Net的C++庫。
- 可以直接用memeset初始化,或用memcpy拷貝