结构体自动化转换为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);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());}
}

View Code

代码中用到了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数组的功能有一点约束条件:

  1. 每个结构体需要提供一个Get函数返回元信息;
  2. 结构体字段如果为指针的话,必须要将指针元素个数放到该字段前面;数组的话也需要提供数组元素个数的字段,因为Get函数会将数组转为指针,数组长度信息会丢掉。

  我觉得这个约束条件相对于它实现的自动化的转换的便利性来说是几乎可以忽略的负担,是微不足道的。

如果你觉得这篇文章对你有用,可以点一下推荐,谢谢。

c++11 boost技术交流群:296561497,欢迎大家来交流技术。

(原创)结构体自动化转为char数组的实现相关推荐

  1. 结构体自动化转为char数组的实现

    结构体自动化转换为char数组这个需求,来自于一个最近开发的一个项目,在项目开发过程中遇到一个小问题,需要将各种结构体拷贝到char数组中,这对于一个简单的结构体来说是很简单的事情,比如下面这个只有整 ...

  2. C语言结构体里的成员数组和指针

    单看这文章的标题,你可能会觉得好像没什么意思.你先别下这个结论,相信这篇文章会对你理解C语言有帮助.这篇文章产生的背景是在微博上,看到@Laruence同学出了一个关于C语言的题,微博链接.微博截图如 ...

  3. C语言学习笔记---结构体中的字符数组和字符指针

      在结构体中可以使用字符数组来存储字符串,也可以使用字符指针来存储字符串.比如: struct str{char s1[5];char s2[5];};struct str str1= {" ...

  4. C# 将结构体转化为byte数组,byte数组转化为结构体

     1.将结构体转化为byte数组 #region /// <summary>/// 结构体转为byte数组/// </summary>/// <typeparam nam ...

  5. C语言结构体及函数传递数组參数演示样例

    C语言结构体及函数传递数组參数演示样例 注:makeSphere()函数返回Sphere结构体,main函数中.调用makeSphere()函数,传递的第一个參数为数组,传递的数组作为指针. post ...

  6. C# 结构体定义 转换字节数组 z

    客户端采用C++开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性. [StructLayoutAttribute(LayoutKind.Sequent ...

  7. linux c之memcpy拷贝结构体到结构体、拷贝字符数组到结构体

    1 memcpy 我们知道这个函数主要是拷贝内存数据,我们一般可以使用拷贝结构体到结构体.也可以拷贝字符数组到结构体,但是这个字符数组数据应该是同一个结构体拷贝先拷贝到这个字符数组,如果是其他格式的字 ...

  8. C++对类(或者结构体)中字符数组赋值时,出现表达式必须是可修改的左值的问题

    最近自己遇到了这类问题,在csdn上找到了很多大神给的解答,非常到位 特别感谢这位: https://blog.csdn.net/JQ_AK47/article/details/53169799 问题 ...

  9. C语言结构体末端定义空数组

    在某些情况,数据结构末端会定义一个可选区域,如下所示: struct abc {int age;char *name[20];...char placeholder[0]; } 而结构体最后使用0长度 ...

最新文章

  1. 深度学习原来还可以这么学!
  2. 对比自监督学习浪潮迅猛来袭,你准备好了吗?
  3. linux下svn客户端安装及环境配置
  4. spark Intellij IDEA及eclipse开发环境搭建
  5. mysql的in和not in的用法(特别注意not in结果集中不能有null)
  6. 将devexpress中控件添加到vs工具箱中
  7. js文件中声明的一个json对象, 在另一个js文件中可以直接使用。
  8. 组图:2007最震撼人心的“史上最牛”事件
  9. Python 分析国庆热门旅游景点,告诉你哪些地方好玩、便宜、人又少!
  10. 网络存储SAN网络存储术语解释
  11. sdut 2493 Constructing Roads (图论)
  12. unity串口 连接多个串口崩溃_必学DB9串口+3种连接方式
  13. EXCEL 统计每日订单量(拉勾教育数据分析实战训练营学习笔记)
  14. java 北京 就业_叩丁狼教育北京Java一期就业报道
  15. 数据压缩作业——浊音,清音,爆破音时频分析
  16. 嵌入式编程与软件编程思想不同浅见
  17. mysql 行转列查询优化_行转列及列转行查询
  18. windows日志捕获工具-DebugView使用教程
  19. Docker从入门到放弃-----Dockerfile常用命令解析与实战(使用docker制作一个开箱即用的consul镜像)
  20. 007 Rust死灵书笔记之引用与别名

热门文章

  1. 三维点集拟合:平面拟合、RANSAC、ICP算法
  2. 1.8(学习笔记)监听器(Listener)
  3. PostgreSQL 数组类型
  4. 一次关于cisco的portfast网络故障
  5. PHP单元测试使用手册
  6. 【php】用filter_var实现的简单参数验证
  7. Android Shape Drawable Resources
  8. squid完全攻略(一)squid优化后详细安装步骤
  9. 用户sa 登陆失败 SQLServer 错误18456----解决方法
  10. [摘自MSDN] ASP.Net2.0学习 [1] 母版页 2 : 创建和使用 ASP.NET 母版页