概述
POD,即Plain Old Data的縮寫,plain代表普通,Old代表舊,從字面意思看是老的、普通的數據類型。這個概念由C++引入主要是為了與C兼容,或者說POD就是與C兼容的那邊部分數據類型。在C++對POD類型進行序列化生成二進制后,在C語言中可以對該二進制進行解析成功。如果對于一個非POD類型,假如包含虛函數的class,大家知道編譯器在操作的時候會加入虛函數指針,但是虛函數這個概念在C語言中不存在,遇到這種數據編譯器就不認識了,或者說對于一個非POD類型的數據,C語言是不識別的,于是C++就提出了POD數據類型的概念。
POD的一種常見用法是跨系統或者跨語言進行通訊,比如與C/.NET等編寫的代碼進行通訊。
概念
structA{ intx; inty; }; structB{ private: intx; public: inty; }; structC{ inta; intb; C(intx,inty):a{x},b{y}{} };
那么,問題來了,上述三個類型A、B和C,哪個是POD類型?
如果我們不清楚POD的判斷標準的話,只能靠猜來回答該問題,幸運的是Modern Cpp提供了接口來進行判斷(PS:下面的trivial和standard layout用來輔助判斷):
C++11 | C++17 | 描述 |
std::is_pod | std::is_pod_v | 通過其value是否為ture來表示是否為POD類型(std::is_pod::value) |
std::is_trivial | std::is_trivial_v | 通過其value是否為ture來表示是否為平凡類型(std::is_trivial::value) |
std::is_standard_layout | std::is_standard_layout_v | 通過其value是否為ture來表示是否為標準布局(std::is_standard_layout::value) |
如果使用上述接口對前面例子中的對象A、B和C進行判斷的話,結果如下:
類型 | Trivial(平凡類型) | Standard layout(標準布局) | POD |
A | 是 | 是 | 是 |
B | 是 | 否 | 否 |
C | 否 | 是 | 否 |
從上述結果可以看出,B是平凡類型,C是標準布局,A既是平凡類型,又是標準布局,同時也是POD類型,這就引出了POD的定義:
A POD type is a type that is both trivial and standard-layout. This definition must hold recursively for all its non-static data members.
通過上述定義可以看出,POD類型既是平凡類型又是標準布局,反過來可以理解為如果一個類型既是平凡類型又是標準布局,且其內部非靜態成員變量也滿足該條件(既是平凡類型又是標準布局),那么這個類型就是POD類型。
標準對POD定義如下:
A POD class is a class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD class (or array thereof). A POD type is a scalar type, a POD class, an array of such a type, or a cv-qualified version of one of these types.
與前一個定義相比,新增了一個類型scalar type,cppference中提到,scalar type為以下幾個之一:
?an arithmetic type
?an enumeration type
?a pointer type
?a pointer-to-member type
?thestd::nullptr_ttype
?cv-qualified versions of the above types
好了,從上面的內容中提到,一個POD類型的類,其非靜態成員變量也必須是POD的,對靜態成員變量和成員函數則沒有這個要求,如下這個類D,其仍然是POD:
structD{ inta; intb; staticstd::strings; intget(){ returna; } };
在本小節中,我們提到了三個概念:Trivial(平凡類型)、Standard layout(標準布局)以及Scalar type,對于最后一個Scalar type比較簡單,所以在后面的內容中,將針對前兩個概念進行詳細分析。
Trivial
這個概念比較抽象,乃至于很難用一句簡單的話來描述。于是查閱了cppreference,顯示標準對這塊也沒有一個完整的定義:
Note: the standard doesn't define a named requirement with this name. This is a type category defined by the core language. It is included here as a named requirement only for consistency.
于是搜索了相關資料,微軟官網對這塊的定義如下:
When a class or struct in C++ has compiler-provided or explicitly defaulted special member functions, then it is a trivial type. It occupies a contiguous memory area. It can have members with different access specifiers. In C++, the compiler is free to choose how to order members in this situation.
也就是說,當一個類型(class/struct)同時滿足以下幾個條件時,它就是trivial type:
?沒有虛函數或虛基類。
?由編譯器生成(使用=default或者=delete)默認的特殊成員函數,包括默認構造函數、拷貝構造函數、移動構造函數、賦值運算符、移動賦值運算符和析構函數。
?數據成員同樣需要滿足條件 1 和 2。
舉例如下:
classA{ public: A(inti):n(i){} A(){} private: intn; }; classB{ public: B(inti):n(i){} B()=default; private: intn; }; classC{ public: C(inti):n(i){} C()=delete; private: intn; }; structBase{ intx; inty; }; structDerived:publicBase { private: intz; }; intmain(){ std::cout<::value<::value<::value<::value<::value<
平凡類型具有如下屬性:
?占據一塊連續的內存區域
?由于對齊要求,成員變量之間可以填充對齊字節(padding)
?可以使用 memcpy進行對象拷貝
?可以將一個平凡的類型通過memcpy()放入char或者unsigned char數組,然后可以把數組內的內容重新組裝成一個該類型對象
?允許有多個不同的訪問控制符,但是,在這種情況下,編譯器有可能對其進行重排
針對上述最后一個屬性,示例如下:
structC{ public: intx; private: inty; public: intz; };
編譯器可以將其重新排序為如下這種:
structC{ public: intx; intz; private: inty; };
正是因為如上原因(訪問權限和編譯器重排),普通類型不能安全的與其他語言編寫的代碼進行交互操作。我們以C語言為例,編譯器的重排導致不能不能與C語言兼容(或者說C語言解析失敗),因為C語言不識別private這個訪問權限,如果進行交互操作,可能會導致其他意想不到的問題。
Standard layout
布局指的是類、結構體或者聯合(Union)的成員在內存中的排列。標準布局定義了這樣一種類型,它不使用C中不存在的而在CPP中存在的某些功能或者特性。如果某個類是標準布局,那么可以通過memcpy進行復制,而且可以與C語言中定義的同種類型進行交互。一言以蔽之,具有標準布局類的類或者結構體等與C兼容,并行可以通過C的API進行交互。
既然符合標準布局的類只具有C語言中存在的功能或者特性,那么,很容易總結出來標準布局的條件:
1.沒有虛函數或者虛基類
2.沒有引用類型的非靜態成員變量
3.所有的非靜態成員變量具有相同的訪問控制權限
4.所有的非靜態成員變量和基類都是標準布局
5.沒有多重繼承導致的菱形問題
6.子類中的第一個非靜態成員的類型與其基類不同
7.在class或者struct繼承時,滿足以下兩種情況之一(總結就是要么子類有非靜態成員變量,要么父類有):
?派生類中有非靜態成員,且只有一個僅包含靜態成員的基類
?基類有非靜態成員,而派生類沒有非靜態成員
現在我們結合示例代碼進行分析:
structA{ }; structB{ Aa; doubleb; }; structC{ voidfoo(){} }; structD:publicC { intx; inty; };
依據前面標準布局的要求,上述幾個類A、B、C和D都是標準布局。現在我們構造稍微復雜點的例子:
structE { intx; }; structF:publicE { inty; }; structG { intx; private: virtualvoidfoo(){}; }; structH{}; structX:publicH{}; structY:publicH{}; structK:publicX,Y{}; structL{ intx; private: inty; };
上面這些例子中,E是標準布局,G不屬于標準布局(虛函數,不滿足條件1),K不屬于標準布局(菱形繼承,不滿足條件5),L不屬于標準布局(不同的訪問權限,不滿足條件3)
接著我們看下前面條件中比較難理解的一個子類中的第一個非靜態成員的類型與其基類不同,示例如下:
structM{ intx; }; structN:publicM{ Mm; inta; }; structX{ intx; }; structY:publicX{ }; structZ:publicX{ inty; };
在上述例子中,M、X和Y是標準布局,而N(不滿足條件6)和Z(不滿足條件7)不是標準類型。
審核編輯:劉清
-
編譯器
+關注
關注
1文章
1634瀏覽量
49130 -
C++語言
+關注
關注
0文章
147瀏覽量
6992 -
POD
+關注
關注
0文章
16瀏覽量
6025
原文標題:我們通常說的 POD 到底是什么?
文章出處:【微信號:CPP開發者,微信公眾號:CPP開發者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論