因udp编程需要发送的内容为char型,而定义的发送数据为结构体,所以需要进行格式转换。


结构体自动化转换为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; p="" 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 
  #include 
  #include 
  namespace Cosmos
  {
  namespace Detail
  {
  struct Functor
  {
  Functor(char* buf, int len) :m_buf(buf), m_bufLen(len)
  {
  }
  template
  typename std::enable_if::value>::type operator()(T t)
  {
  FillForward(t);
  }
  template
  typename std::enable_if<std::is_class::value>::type operator()(T& t)
  {
  FillForward(t);
  }
  private:
  template
  void FillForward(T&& t)
  {
  if (std::is_same::value || std::is_same::value)
  m_latestInt = t;
  FillIn(std::forward(t), m_buf + m_curPos);
  m_curPos += sizeof(T);
  }
  template
  typename std::enable_if<std::is_integral::value>::type FillIn(T t, char* p)
  {
  *((T*) p) = t;
  }
  template
  typename std::enable_if<std::is_floating_point::value>::type FillIn(T t, char* p)
  {
  if(std::is_same::value)
  sprintf(p, "%.15f", t);
  else
  sprintf(p, "%f", t);
  }
  template
  typename std::enable_if<std::is_pointer::value&&std::is_class<typename std::remove_pointer::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 std::enable_if<std::is_pointer::value&&std::is_arithmetic<typename std::remove_pointer::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
  void Fill(T t, char* p, F&& f)
  {
  int count = m_latestInt.AnyCast();
  using U = typename std::remove_pointer::type;
  for (int i = 0; i < count; i++)
  {
  f(i, p, sizeof(U));
  }
  }
  template
  typename std::enable_if<std::is_pointer::value&&std::is_void<typename std::remove_pointer::type>::value>::type FillIn(T t,
  char* p)
  {
  int count = m_latestInt.AnyCast();
  int* temp = (int*) t;
  for (int i = 0; i < count; i++)
  {
  FillIn(temp[i], p + i*sizeof(int));
  }
  }
  template
  typename std::enable_if<std::is_same::value>::type FillIn(T& t, char* p)
  {
  strcpy(p, t.c_str());
  }
  template
  typename std::enable_if<std::is_class::value&&!std::is_same::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
  void for_each_impl(Func&& f, Last&& last)
  {
  f(last);
  }
  template
  void for_each_impl(Func&& f, First&& first, Rest&&...rest)
  {
  f(first);
  for_each_impl(std::forward(f), rest...);
  }
  template
  void for_each_helper(Func&& f, IndexTuple, std::tuple&& tup)
  {
  for_each_impl(std::forward(f), std::forward(std::get(tup))...);
  }
  }
  template
  void tp_for_each(Func&& f, std::tuple& tup)
  {
  using namespace details;
  for_each_helper(forward(f), typename make_indexes::type(), std::tuple(tup));
  }
  template
  void tp_for_each(Func&& f, std::tuple&& tup)
  {
  using namespace details;
  for_each_helper(forward(f), typename make_indexes::type(), forward<std::tuple>(tup));
  }
  template
  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数组的功能有一点约束条件:

  每个结构体需要提供一个Get函数返回元信息;

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

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



对C的指针一直有所欠缺,这里学到一些皮毛,分享一下:

结构体:

#defined MAX_LENGTH 200
typedef struct _TEST_EXAMPLE
{
    char name[MAX_LENGTH+1];
    int age;
} test_example;



结构体的首位置转换成Char指针,比如有10位同学:

test_example *te = (test_example *)malloc(sizeof(test_example) * 10);
//...结构体读取数据..
for (size_t i = 0; i < 10; i ++)
{
te[i].name = student[0];
te[i++].age = student[1];
}
char *result = (char *)te;



当用指针(char *)result做了一些操作后,最后要将Char*指到的首位和长度还原成结构体数组(结构体数组在内存中是连续的);打印结构体如下:
方法一:

size_t value_length = sizeof(test_example) * 10;
for (size_t i = 0; i < value_length; i ++)
{
    printf("%s\t", ((test_example *)(result + i))->name);
    printf("%d\n", ((test_example *)(result + i))->age);
    i += sizeof(test_example) - 1;
}



另外一种方法:

test_example *tmp = (test_example *)result;
for (size_t i = 0; i < value_length / sizeof(test_example); i ++)
{
    printf("%s\t%d\n", (tmp + i)->name, (tmp + i)->age);
}



看起来非常简单,起先我是使用纯指针去截取每个name和age的位置,但是结构体有个问题,随平台不同,结构体每个对象中所占用的空间也不一样,一般是int的整数倍,比如char a[10]是结构的唯一成员变量,其实相当于结构体仍然需要占用12个字节,具体bool这种类型是否也是int的整数倍,需要进行测试确认;所以最好的办法还是使用直接OO的方法指定,虽然指针也是一种OO。