结构体自动化转换为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. TL 重构

    import dependencies.*;import org.apache.commons.lang3.ArrayUtils;import org.apache.commons.lang3.Str ...

  2. Spring MVC测试框架详解——服务端测试

    随着RESTful Web Service的流行,测试对外的Service是否满足期望也变的必要的.从Spring 3.2开始Spring了Spring Web测试框架,如果版本低于3.2,请使用sp ...

  3. hibernate 双向 1-n(具体分析)

    双向 1-n 与 双向 n-1 是全然同样的两种情形 双向 1-n 须要在 1 的一端能够訪问 n 的一端, 反之依旧. 域模型:从 Order 到 Customer 的多对一双向关联须要在Order ...

  4. EntityFramework 5.0 CodeFirst 教程01-搭建环境和快速上手

    ----------------------------目录------------------------------ EntityFramework 5.0 CodeFirst 教程03-数据结构 ...

  5. [转载]在VirtualBox中收缩虚拟磁盘映像文件

    原文地址:在VirtualBox中收缩虚拟磁盘映像文件作者:bobby 由于经常要测试一些软件,我在VirtualBox虚拟机中安装了一套Windows.使用过虚拟机的朋友都知道,为了节省硬盘空间,一 ...

  6. NSNotificationCenter实现原理

    # 前言 Cocoa中使用NSNotification.NSNotificationCenter和KVO来实现观察者模式,实现对象间一对多的依赖关系. 本篇文章主要来讨论NSNotification和 ...

  7. hadoop遇到的问题及处理

    1:杀掉hadoop作业 列出作业 ./hadoop job -list 杀掉 ./hadoop job -kill job_id 1:某些节点出现running asprocess XXX. Sto ...

  8. easyui combobox 动态加载的两种方法

    reload 方法 javascript代码 //指定id 和 text 否则始终选择第一个 $('#contact_city').combobox({ valueField:'id', textFi ...

  9. memcache 与 redis 为web app 带来的性能提升

    memcache 与 redis 为web app 带来的性能提升 参考: 1. http://www.cnblogs.com/ToDoToTry/p/3513688.html

  10. spring MVC中传递的参数对象中包含list的情况

    测试需要的jar包:spring 3.2.jar +  jackson-all-1.8.5.jar. 写代码时碰到个需要将对象里的子明细一起传递到controller里去,当时就想直接将参数一起传递过 ...