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

struct A
{
int a;
int b;
}; char buf[];
A a = {,};
memcpy(buf, &a, sizeof(A));

  一句memcpy就能将结构体a拷贝到char数组中去了,直接通过memcpy拷贝结构体只对于内存连续的结构体有效。如果结构体内存不连续,结构体中含有double、string、指针甚至嵌套结构体时,直接拷贝是错误的,这时需要一个一个字段的转换,比如下面的结构体:

struct A
{
int x;
string y;
};
char buf[];
A a = {, "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[];
B b = {, , new int[]{, }, {, }};
char* p = buf;
*((int*)p) = b.x;
p+=sizeof(int);
*((int*)p) = b.count;
p+=sizeof(int);
for(int i=; 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 = ; 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 = ; 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 = ;
Any m_latestInt = ;
}; 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 = { , 1.256, , new int[]{, }, {, 5.36} };
char buf[];
Put(buf, sizeof(buf), t); char buf1[];
MySubStruct* st = new MySubStruct[];;
st[] = { , };
st[] = { , };
MyStruct2 t2 = { , , , st };
Put(buf1, sizeof(buf1), t2); int* p3 = new int[]{,};
MyStruct3 t3 = { false, , (void*) p3 };
char buf3[];
Put(buf3, sizeof(buf3), t3); MyStruct4 t4 = { , "test" };
char buf4[];
Put(buf4, sizeof(buf4), t4);
}

  可以看到不管结构体有多少字段,还是是否含有字符串、指针或者嵌套了结构体,都可以通过一个Put函数搞定,没有了繁琐而又重复的赋值和偏移操作,不用担心偏移错了,整个繁琐的过程程序都自动化的完成了,简单利落。
  要用这个自动化的将结构体转换为char数组的功能有一点约束条件:

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

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

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

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

(原创)结构体自动化转为char数组的实现的更多相关文章

  1. 深入理解指针—>结构体里的成员数组和指针

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

  2. C# 调用C/C++动态链接库,结构体中的char*类型

    用C#掉用C++的dll直接import就可以之前有不同的类型对应,当要传递结构体的时候就有点麻烦了,这里有一个结构体里边有char*类型,这个类型在C#中调用没法声明,传string是不行的默认st ...

  3. 读陈浩的《C语言结构体里的成员数组和指针》总结,零长度数组

    原文链接:C语言结构体里的成员数组和指针 复制例如以下: 单看这文章的标题,你可能会认为好像没什么意思.你先别下这个结论,相信这篇文章会对你理解C语言有帮助.这篇文章产生的背景是在微博上,看到@Lar ...

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

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

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

    struct test{ int i; char *p; }; struct test *str; ; char *b = "ioiodddddddddddd"; str = (s ...

  6. JNA结构体参数传递,Java数组

    JNA以结构体数组为参数进行调用: ////// C++ // student 结构体定义 typedef struct { int age; char name[20]; }Student; //  ...

  7. 结构体的malloc与数组空间

    结构体的malloc 如果结构体中有指针,对结构体的malloc 和其指针成员变量的malloc是没有关系的 结构体malloc的是存储自己地址的 忘记了面试常考试的sizeof的几个主要点 ==== ...

  8. C++/C#结构体转化-二维数组-bytes To Strings

    C++结构体 typedef struct VidyoClientRequestGetWindowsAndDesktops_ { /*! The number of application windo ...

  9. C++结构体与Delphi结构体相互传参,结构体中包含结构体的嵌套,数组指针

    //结构体的声明 typedef struct Mwinddirectbaseline { char* p; int s; int i; }Mwinddirectbaseline; typedef s ...

随机推荐

  1. 利用Git进行团队协作

    前言: 这里简单介绍一下Git的历史. 同生活中的许多伟大事件一样,Git 诞生于一个极富纷争大举创新的年代.Linux 内核开源项目有着为数众广的参与者.绝大多数的 Linux 内核维护工作都花在了 ...

  2. Oracle 中TNS的作用

    什么是TNS? TNS是Oracle Net的一部分,专门用来管理和配置Oracle数据库和client连接的一个工具,在大多数情况下client和数据库要通讯,必须配置TNS,当然在少数情况下,不用 ...

  3. T-Sql常用语句

    1.用bcp导出txt数据 DECLARE ), ) BEGIN , ), '/', '-'); SET @bcp = 'bcp WebStat.dbo.[PV_HIS_' + @date + '] ...

  4. java实现读取ftp服务器上的csv文件

    定义ftp操作接口 import java.io.InputStream; import java.util.List; import org.apache.commons.net.ftp.FTPCl ...

  5. cnpm不是内部或外部命令 cnpm: command not found

    问题是处在于 你没用用淘宝的镜像 安装cnpm 不信 你打下cnpm -v,  看是  是不是也不是内部命令: 好了,那就安装下吧 npm install cnpm -g --registry=htt ...

  6. [转载]CentOS6&nbsp;快速搭建轻量级远程桌面&nbsp;Xfce&nb

    原文地址:CentOS6 快速搭建轻量级远程桌面 Xfce & VNC & Firefox作者:哈囉健一 0.系统信息 CentOS Linux release 6.0 (Final) ...

  7. commit your changes or stash them before you can merge

    今天用git pull来更新代码,遇到了下面的问题: 今天git pull 出现以下问题 Please commit your changes or stash them before you mer ...

  8. Java获取函数参数名称

    原理 编译之后的class文件默认是不带有参数名称信息的,使用 IDE 时,反编译jar包得到的源代码函数参数名称是 arg0,arg1......这种形式,这是因为编译 jar 包的时候没有把符号表 ...

  9. 【RS】Modeling User Exposure in Recommendation - 在推荐中建模用户的暴露程度

    [论文标题]Modeling User Exposure in Recommendation (2016-WWW) [论文作者]Dawen Liang,Laurent Charlin,James Mc ...

  10. iOS 10 的一个重要更新-自定义的通知界面

    续上篇,在简单闹钟的例子上,在通知界面上显示图片动画,并用通知关联的按钮更新通知界面.介绍 iOS 10 通知 API 的扩展:自定义通知显示界面. 新框架可以统一处理本地通知和远程推送,同时增加了一 ...