结构体自动化转换为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,欢迎大家来交流技术。