結構體自動化轉換爲char數組這個需求,來自於一個最近開發的一個項目,在項目開發過程當中遇到一個小問題,須要將各類結構體拷貝到char數組中,這對於一個簡單的結構體來講是很簡單的事情,好比下面這個只有整形字段的結構體:html
struct A { int a; int b; }; char buf[100]; A a = {1,2}; memcpy(buf, &a, sizeof(A));
一句memcpy就能將結構體a拷貝到char數組中去了,直接經過memcpy拷貝結構體只對於內存連續的結構體有效。若是結構體內存不連續,結構體中含有double、string、指針甚至嵌套結構體時,直接拷貝是錯誤的,這時須要一個一個字段的轉換,好比下面的結構體:c++
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());
能夠看到這種一個一個字段轉換的方法是很繁瑣的,字段越多轉換就越繁瑣,並且偏移量還容易寫錯。當結構體字段是指針或者結構體時就更繁瑣了,好比下面的結構體:c#
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方法來返回其字段的基本信息,好比:ide
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,想查看能夠點這裏。測試
再看看測試代碼:this
//嵌套的結構體 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數組的功能有一點約束條件:spa
我以爲這個約束條件相對於它實現的自動化的轉換的便利性來講是幾乎能夠忽略的負擔,是微不足道的。指針
若是你以爲這篇文章對你有用,能夠點一下推薦,謝謝。
c++11 boost技術交流羣:296561497,歡迎你們來交流技術。