結構體自動化轉換為char數組這個需求,來自於一個最近開發的一個項目,在項目開發過程中遇到一個小問題,需要將各種結構體拷貝到char數組中,這對於一個簡單的結構體來說是很簡單的事情,比如下面這個只有整形字段的結構體:
struct A { int a; int b; }; char buf[100]; A a = {1,2}; memcpy(buf, &a, sizeof(A));
一句memcpy就能將結構體a拷貝到char數組中去了,直接通過memcpy拷貝結構體只對於內存連續的結構體有效。如果結構體內存不連續,結構體中含有double、string、指針甚至嵌套結構體時,直接拷貝是錯誤的,這時需要一個一個字段的轉換,比如下面的結構體:
struct A { int x; string y; }; char buf[100]; A a = {1, "test"}; char* p = buf; *((int*)p) = x; p+=sizeof(int); strcpy(p, t.c_str());
可以看到這種一個一個字段轉換的方法是很繁瑣的,字段越多轉換就越繁瑣,而且偏移量還容易寫錯。當結構體字段是指針或者結構體時就更繁瑣了,比如下面的結構體:
struct A { int x; int y; }; struct B { int x; int count; //標示指針p中的元素個數 int *p; A a; }; char buf[100]; B b = {1, 2, new int[2]{3, 4}, {2, 3}}; char* p = buf; *((int*)p) = b.x; p+=sizeof(int); *((int*)p) = b.count; p+=sizeof(int); for(int i=0; i<count; i++) { *((int*)p) = b.p[i]; } p+=sizeof(int)*b.count; *((int*)p) = b.a.x; p+=sizeof(int); *((int*)p) = b.a.y;
可以看到這種帶指針或者嵌套結構體的結構體轉換為char數組時非常繁瑣,而且很容易出錯,其實大部分的工作都是重復的,比如不斷的賦值與偏移。這個過程如果能做到自動化就很方便了,直接傳一個結構體,然后自動將結構體中的字段一個一個拷貝到數組中,不必關心偏移是否出錯了,也不用關心內部的字符串或者嵌套結構體如何拷貝等細節,總之 ,只要傳一個結構體就能自動化的將其轉換為char數組。
這種自動化的轉換不僅僅大大降低了轉換的復雜度還解決了手工拷貝的時候容易出錯的問題,程序自動化的拷貝保證了拷貝的正確性。目前我還沒看到有類似的轉換類或者函數能夠自動地很方便地將復雜結構體轉換為char數組,想想自己實現一個也不是難事,其實要實現自動轉換最關鍵的是要獲取結構體的元信息,有了元信息我們就能對結構體中的每個字段進行轉換了。在c#中可以通過反射很方便的獲取結構體的元信息,而c++中沒有反射,就要想其它辦法來獲取元信息了。而且這個獲取元信息的方法還要非常簡單,幾乎不增加額外的負擔。這里我是通過tuple來獲取原信息,要求每個結構體要定義一個Get方法來返回其字段的基本信息,比如:
struct A { int x; double y; auto Get()->decltype(std::make_tuple(x,y)) { return std::make_tuple(x,y); } };
這個Get方法非常簡單,只要將字段放到tuple中返回出去就行了,幾乎沒有增加額外的負擔,這個看似簡單函數卻有着巨大的作用,只要有這個Get函數我就可以獲取結構體的基本元信息了,有了這個以后,所有的轉換都可以實現自動化了。還是來看看具體是如何實現自動化的轉換吧:

#include <type_traits> #include <TpForeach.hpp> #include <Any.hpp> namespace Cosmos { namespace Detail { struct Functor { Functor(char* buf, int len) :m_buf(buf), m_bufLen(len) { } template<typename T> typename std::enable_if<!std::is_class<T>::value>::type operator()(T t) { FillForward(t); } template<typename T> typename std::enable_if<std::is_class<T>::value>::type operator()(T& t) { FillForward(t); } private: template<typename T> void FillForward(T&& t) { if (std::is_same<T, int>::value || std::is_same<T, unsigned int>::value) m_latestInt = t; FillIn(std::forward<T>(t), m_buf + m_curPos); m_curPos += sizeof(T); } template<typename T> typename std::enable_if<std::is_integral<T>::value>::type FillIn(T t, char* p) { *((T*) p) = t; } template<typename T> typename std::enable_if<std::is_floating_point<T>::value>::type FillIn(T t, char* p) { if(std::is_same<T,double>::value) sprintf(p, "%.15f", t); else sprintf(p, "%f", t); } template<typename T> typename std::enable_if<std::is_pointer<T>::value&&std::is_class<typename std::remove_pointer<T>::type>::value>::type FillIn(T t, char* p) { Fill(t, p, [this, &t](int i, char* p, int size){Put(p + i*size, size, t[i]); }); } template<typename T> typename std::enable_if<std::is_pointer<T>::value&&std::is_arithmetic<typename std::remove_pointer<T>::type>::value>::type FillIn(T t, char* p) { Fill(t, p, [this, &t](int i, char* p, int size){FillIn(t[i], p + i*size); }); } template<typename T, typename F> void Fill(T t, char* p, F&& f) { int count = m_latestInt.AnyCast<int>(); using U = typename std::remove_pointer<T>::type; for (int i = 0; i < count; i++) { f(i, p, sizeof(U)); } } template<typename T> typename std::enable_if<std::is_pointer<T>::value&&std::is_void<typename std::remove_pointer<T>::type>::value>::type FillIn(T t, char* p) { int count = m_latestInt.AnyCast<int>(); int* temp = (int*) t; for (int i = 0; i < count; i++) { FillIn(temp[i], p + i*sizeof(int)); } } template<typename T> typename std::enable_if<std::is_same<std::string, T>::value>::type FillIn(T& t, char* p) { strcpy(p, t.c_str()); } template<typename T> typename std::enable_if<std::is_class<T>::value&&!std::is_same<std::string, T>::value>::type FillIn(T& t, char* p) { Put(p, sizeof(T), t); } char* m_buf; int m_bufLen; int m_curPos = 0; Any m_latestInt = 0; }; template<typename Func, typename Last> void for_each_impl(Func&& f, Last&& last) { f(last); } template<typename Func, typename First, typename ... Rest> void for_each_impl(Func&& f, First&& first, Rest&&...rest) { f(first); for_each_impl(std::forward<Func>(f), rest...); } template<typename Func, int ... Indexes, typename ... Args> void for_each_helper(Func&& f, IndexTuple<Indexes...>, std::tuple<Args...>&& tup) { for_each_impl(std::forward<Func>(f), std::forward<Args>(std::get<Indexes>(tup))...); } } template<typename Func, typename ... Args> void tp_for_each(Func&& f, std::tuple<Args...>& tup) { using namespace details; for_each_helper(forward<Func>(f), typename make_indexes<Args...>::type(), std::tuple<Args...>(tup)); } template<typename Func, typename ... Args> void tp_for_each(Func&& f, std::tuple<Args...>&& tup) { using namespace details; for_each_helper(forward<Func>(f), typename make_indexes<Args...>::type(), forward<std::tuple<Args...>>(tup)); } template<typename T> void Put(char* p, int len, T&& t) { using namespace Detail; tp_for_each(Functor(p, len), t.Get()); } }
代碼中用到了Any,這個Any就是我前面的博文中實現的Any,想查看可以點這里。
再看看測試代碼:
//嵌套的結構體 struct MySubStruct { int a; double b; auto Get()->decltype(std::make_tuple(a, b)) { return std::make_tuple(a, b); } }; //含指針和嵌套結構體的結構體 struct MyStruct { int a; double b; int count; //對於指針,必須將指針元素個數count放在指針元素的前面 int* p; MySubStruct t; auto Get()->decltype(std::make_tuple(a, b, count, p, t)) { return std::make_tuple(a, b, count, p, t); } }; //嵌套結構體指針的結構體 struct MyStruct2 { int a; double b; int count;//對於指針,必須將指針元素個數count放在指針元素的前面 MySubStruct* t; auto Get()->decltype(std::make_tuple(a, b, count, t)) { return std::make_tuple(a, b, count, t); } }; //含void指針的結構體 struct MyStruct3 { bool r; int a;//對於指針,必須將指針元素個數count放在指針元素的前面 void* b; auto Get()->decltype(std::make_tuple(r,a, b)) { return std::make_tuple(r, a, b); } }; //含字符串的結構體 struct MyStruct4 { int a; string b; auto Get()->decltype(std::make_tuple(a, b)) { return std::make_tuple(a, b); } }; void TestArray() { MyStruct t = { 9, 1.256, 2, new int[2]{3, 4}, {14, 5.36} }; char buf[100]; Put(buf, sizeof(buf), t); char buf1[100]; MySubStruct* st = new MySubStruct[2];; st[0] = { 6, 7 }; st[1] = { 8, 9 }; MyStruct2 t2 = { 3, 4, 2, st }; Put(buf1, sizeof(buf1), t2); int* p3 = new int[2]{5,6}; MyStruct3 t3 = { false, 2, (void*) p3 }; char buf3[100]; Put(buf3, sizeof(buf3), t3); MyStruct4 t4 = { 6, "test" }; char buf4[100]; Put(buf4, sizeof(buf4), t4); }
可以看到不管結構體有多少字段,還是是否含有字符串、指針或者嵌套了結構體,都可以通過一個Put函數搞定,沒有了繁瑣而又重復的賦值和偏移操作,不用擔心偏移錯了,整個繁瑣的過程程序都自動化的完成了,簡單利落。
要用這個自動化的將結構體轉換為char數組的功能有一點約束條件:
- 每個結構體需要提供一個Get函數返回元信息;
- 結構體字段如果為指針的話,必須要將指針元素個數放到該字段前面;數組的話也需要提供數組元素個數的字段,因為Get函數會將數組轉為指針,數組長度信息會丟掉。
我覺得這個約束條件相對於它實現的自動化的轉換的便利性來說是幾乎可以忽略的負擔,是微不足道的。
如果你覺得這篇文章對你有用,可以點一下推薦,謝謝。
c++11 boost技術交流群:296561497,歡迎大家來交流技術。