结构体自动化转为char数组的实现
? ? 結(jié)構(gòu)體自動(dòng)化轉(zhuǎn)換為char數(shù)組這個(gè)需求,來自于一個(gè)最近開發(fā)的一個(gè)項(xiàng)目,在項(xiàng)目開發(fā)過程中遇到一個(gè)小問題,需要將各種結(jié)構(gòu)體拷貝到char數(shù)組中,這對(duì)于一個(gè)簡(jiǎn)單的結(jié)構(gòu)體來說是很簡(jiǎn)單的事情,比如下面這個(gè)只有整形字段的結(jié)構(gòu)體:
struct A {int a;int b; };char buf[100]; A a = {1,2}; memcpy(buf, &a, sizeof(A));? ? 一句memcpy就能將結(jié)構(gòu)體a拷貝到char數(shù)組中去了,直接通過memcpy拷貝結(jié)構(gòu)體只對(duì)于內(nèi)存連續(xù)的結(jié)構(gòu)體有效。如果結(jié)構(gòu)體內(nèi)存不連續(xù),結(jié)構(gòu)體中含有double、string、指針甚至嵌套結(jié)構(gòu)體時(shí),直接拷貝是錯(cuò)誤的,這時(shí)候需要一個(gè)一個(gè)字段的轉(zhuǎn)換,比如下面的結(jié)構(gòu)體:
struct A {int x;string y; }; char buf[100]; A a = {1, "test"}; char *p = buf; *((int *)p) = x; p += sizeof(int); strcpy(p, y.c_str());? ? 可以看到這種一個(gè)一個(gè)字段轉(zhuǎn)換的方法是很繁瑣的,字段越多轉(zhuǎn)換就越繁瑣,而且偏移量還容易寫錯(cuò)。當(dāng)結(jié)構(gòu)體字段是指針或者結(jié)構(gòu)體時(shí)就更繁瑣了,比如下面的結(jié)構(gòu)體:
struct A {int x;int y; }; struct B {int x;int count; //標(biāo)識(shí)指針p中的元素個(gè)數(shù)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;? ? 可以看到這種帶指針或者嵌套結(jié)構(gòu)體的結(jié)構(gòu)體轉(zhuǎn)換為char數(shù)組時(shí)非常繁瑣,而且很容易出錯(cuò),其實(shí)大部分的工作都是重復(fù)的,比如不斷的賦值與偏移。這個(gè)過程如果能做到自動(dòng)化就很方便了,直接傳一個(gè)結(jié)構(gòu)體,然后自動(dòng)將結(jié)構(gòu)體中的字段一個(gè)一個(gè)拷貝到數(shù)組中,必須關(guān)心偏移是否出錯(cuò)了,也不用關(guān)心內(nèi)部的字符串或者嵌套結(jié)構(gòu)體如何拷貝等細(xì)節(jié),總之,只要傳一個(gè)結(jié)構(gòu)體就能自動(dòng)化的將其轉(zhuǎn)換為char數(shù)組。
? ? 這種自動(dòng)化的轉(zhuǎn)換不僅僅大大降低了轉(zhuǎn)換的復(fù)雜度還解決了手工拷貝的時(shí)候容易出錯(cuò)的問題,程序自動(dòng)化的拷貝保證了拷貝的正確性。目前我還沒看到有類似的轉(zhuǎn)換類或者函數(shù)能夠自動(dòng)地很方便地將復(fù)雜結(jié)構(gòu)體轉(zhuǎn)換為char數(shù)組,想想自己實(shí)現(xiàn)一個(gè)也不是難事,其實(shí)要實(shí)現(xiàn)自動(dòng)轉(zhuǎn)換最關(guān)鍵的是要獲取結(jié)構(gòu)體的元信息,有了元信息我們就能對(duì)結(jié)構(gòu)體中的每個(gè)字段進(jìn)行轉(zhuǎn)換了。在c#中可以通過反射很方便的獲取結(jié)構(gòu)體的元信息,而c++中沒有反射,就要想其它辦法來獲取元信息了。而且這個(gè)獲取元信息的方法還要非常簡(jiǎn)單,幾乎不增加額外的負(fù)擔(dān)。這里我是通過tuple來獲取元信息,要求每個(gè)結(jié)構(gòu)體要定一個(gè)Get方法來返回其字段的節(jié)本信息,比如:
struct A {int x;double y;auto Get()->decltype(std::make_tuple(x,y)){return std::make_tuple(x,y);} };? ? 這個(gè)Get方法非常簡(jiǎn)單,只要將字段放到tuple中返回出去就行了,幾乎沒有增加額外的負(fù)擔(dān),這個(gè)看似簡(jiǎn)單函數(shù)卻有著巨大的作用,只要有這個(gè)Get函數(shù)我就可以獲取結(jié)構(gòu)體的基本元信息了,有了這個(gè)以后,所有的轉(zhuǎn)換都可以實(shí)現(xiàn)自動(dòng)化了。還是來看看具體是如何實(shí)現(xiàn)自動(dòng)化的轉(zhuǎn)換吧:
#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);elsesprintf(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,這個(gè)Any就是我前面的博文中實(shí)現(xiàn)的Any,想查看可以點(diǎn)這里。
再看看測(cè)試代碼:
//嵌套的結(jié)構(gòu)體 struct MySubStruct {int a;double b;auto Get()->decltype(std::make_tuple(a, b)){return std::make_tuple(a, b);} };//含指針和嵌套結(jié)構(gòu)體的結(jié)構(gòu)體 struct MyStruct {int a;double b;int count; //對(duì)于指針,必須將指針元素個(gè)數(shù)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);} };//嵌套結(jié)構(gòu)體指針的結(jié)構(gòu)體 struct MyStruct2 {int a;double b;int count; //對(duì)于指針,必須將指針元素個(gè)數(shù)count放在指針元素的前面MySubStruct *t;auto Get()->decltype(std::make_tuple(a, b, count, t)){return std::make_tuple(a, b, count, t);} };//含void指針的結(jié)構(gòu)體 struct MyStruct3 {bool r;int a; //對(duì)于指針,必須將指針元素個(gè)數(shù)count放在指針元素的前面void *b;auto Get()->decltype(std::make_tuple(r, a, b)){return std::make_tuple(r, a, b);} };//含字符串的結(jié)構(gòu)體 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); }? ? 可以看到不管結(jié)構(gòu)體有多少字段,還是是否含有字符串、指針或者嵌套了結(jié)構(gòu)體,都可以通過一個(gè)Put函數(shù)搞定,沒有了繁瑣而又重復(fù)的賦值和偏移操作,不用擔(dān)心偏移錯(cuò)了,整個(gè)繁瑣的過程程序都自動(dòng)化的完成了,簡(jiǎn)單利落。
? ? 要用這個(gè)自動(dòng)化的將結(jié)構(gòu)體轉(zhuǎn)換為char數(shù)組的功能有一點(diǎn)約束條件:
1.每個(gè)結(jié)構(gòu)體需要提供一個(gè)Get函數(shù)返回元信息;
2.結(jié)構(gòu)體字段如果為指針的話,必須要將指針元素個(gè)數(shù)放到該字段前面;數(shù)組的話也需要提供數(shù)組元素個(gè)數(shù)的字段,因?yàn)镚et函數(shù)會(huì)將數(shù)組轉(zhuǎn)為指針,數(shù)組長(zhǎng)度信息會(huì)丟掉。
我覺得這個(gè)約束條件相對(duì)于它實(shí)現(xiàn)的自動(dòng)化的轉(zhuǎn)換的便利性來說是幾乎可以忽略的負(fù)擔(dān),是微不足道的。
總結(jié)
以上是生活随笔為你收集整理的结构体自动化转为char数组的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 清理apache共享内存引起的oracl
- 下一篇: struct结构体和char型数组的相互