C++对象模型小探索

class base {
public:
    base():a(10),b(11){}
    virtual void fa(){ std::cout << "base a function...\n";}
    virtual void fb(){ std::cout << "base b function...\n";}
    
public:
    int a;
    int b;
    std::string c{"angel"};
};

class derived: public base {
public:
    void fa() override { std::cout << "derived a function ...\n";}
    void fb() override { std::cout << "derived b function...\n"; }
    void fc() { std::cout << "derived c function...\n";}
public:
    int c;
};



int main(int argc, char** argv)
{
    std::cout << "class base size : " << sizeof(base) << std::endl;
    std::cout << "class derived size: " << sizeof(derived) << std::endl;
    
    derived* pd = new derived();
    base* pb = new derived();
 
    using func= void(*)();
    
    long int vptr1 = reinterpret_cast<long int>(pb);
    long int vptr2 = reinterpret_cast<long int>(pd);
    
    long int* p = (long int*)vptr1;
    long int* p1 = (long int*)vptr2;
    std::cout << "p  " <<  p << std::endl;
    std::cout << "p1 " <<  p1 << std::endl;
    
    long int* vtaddr = (long int*)(*p);
    long int* vtaddr2 = (long int*)(*p1);
    std::cout << "vtaddr  " << vtaddr << std::endl;
    std::cout << "vtaddr2 " << vtaddr2 << std::endl;
    
    
    func* ptr = (func*)(vtaddr);
    (*ptr)();
    func* ptrb = (func*)(vtaddr+1);
    (*ptrb)();
    
    
    func* bptr = (func*)(vtaddr2);
    (*bptr)();
    func* bptrb = (func*)(vtaddr2+1);
    (*bptrb)();
    
    std::cout << *(int*)(long int*)(vptr1+8) << std::endl;
    std::cout << *(int*)(long int*)(vptr1+12) << std::endl;
    std::cout << std::string((char*)(long int*)(vptr1+16))<< std::endl;
    
    
    pd->fa();
    pb->fa();
    
    

    return 0;
}


以上代码强制调用vtable指向的函数,从安全性角度讲:可以替换虚表的任何函数。

结果是:

 class base size : 40
 class derived size: 48
 p  0x102a84240
 p1 0x102a84210
 vtaddr  0x10000b090
 vtaddr2 0x10000b090
 derived a function ...value is ...34
 derived b function...
 derived a function ...value is ...-1746446496
 derived b function...
 10
 11
 
 angel
 derived a function ...value is ...10
 derived a function ...value is ...10

强行使用裸指针调用,得到的成员值明显不对。推测是没有代入隐含的this指针。

下次谈谈如何基于本案列做C++的逆向工程。

我对C/C++语言运算规则另类小解

先看看无聊的C运算符优先级和结合性。看到表1的人可以分为三类:

  • C语言编译器的作者
  • 计算机入门者
  • 各种计算机基础能力考试应对者

比如要你计算: -Num++, +Num++, f( a(), b(), c()) ,int a= c,d,e; 之类的运算顺序。

大多数工程师为了避免记忆这繁琐的规则,使用()括号大法来解决大部分运算符优先级/结合性的问题。而且在大多数情况下,商业团队会通过统一的编程规范来避免程序语言的”坑”。

表1:运算符优先级和结合性

优先级 运算符 描述 结合性
1 ++ -- 后缀自增与自减 从左到右
() 函数调用
[] 数组下标
. 结构体与联合体成员访问
-> 结构体与联合体成员通过指针访问
(type){list} 复合字面量(C99)
2 ++ -- 前缀自增与自减[注 1] 从右到左
+ - 一元加与减
! ~ 逻辑非与逐位非
(type) 转型
* 间接(解引用)
& 取址
sizeof 取大小[注 2]
_Alignof 对齐要求(C11)
3 * / % 乘法、除法及余数 从左到右
4 + - 加法及减法
5 << >> 逐位左移及右移
6 < <= 分别为 < 与 ≤ 的关系运算符
> >= 分别为 > 与 ≥ 的关系运算符
7 == != 分别为 = 与 ≠ 关系
8 & 逐位与
9 ^ 逐位异或(排除或)
10 | 逐位或(包含或)
11 && 逻辑与
12 || 逻辑或
13 ?: 三元条件[注 3] 从右到左
14[注 4] = 简单赋值
+= -= 以和及差赋值
*= /= %= 以积、商及余数赋值
<<= >>= 以逐位左移及右移赋值
&= ^= |= 以逐位与、异或及或赋值
15 , 逗号 从左到右

去记忆这种表格明显是反直觉的,我们还是得理解编译器帮我们解释符号的意图。

C的符号可以归结为几类:

  1. 算术运算符。最普通的+ – * / % ~ & | ^ << >>
  2. 逻辑运算符。! && ||
  3. 赋值运算符。= &= <<=
  4. 比较运算符。< == >= <= > !=
  5. 成员访问。 [] () * -> .
  6. 其他 … , (type强转), sizeof, _Alignof

先是结合性,原则很简单,也很人类。

就是按照普通人的阅读顺序去记忆: 比如[], 它肯定是从左到右结合。因为 一个表达式 a[1], 先要知道a,才能知道相对寻址的 a[1]。 再比如算术运算a + b, 肯定是先move a到寄存器,在把a和b想加。因此也是从左向右结合。

那么,哪些是从右往左结合呢?

我们写代码 sizeof( SuperStruct ), 肯定是先想到SuperStruct的大小和地址在哪,然后再去取大小。这就是很直接的计算机思维

最有代表性的是: 前++,前– VS 后++, 后–

用他们来测试下这种规律的正确性:

a++ , 从左到右。因为先得知道a,才能++。

–a , 从右往左结合。 因为先得知道a,才能–。

看,这是不用记忆的。非常直接的计算机符号逻辑

优先级的记忆

优先级最高的: 除了后加/后减的优先级超高。 成员访问优先级高完全符合直觉。成员访问优先级高,可以理解为得先有数据才能做各种运算

优先级次高的: 几乎全部单目运算符优先级是次高的。单目运算符只需要一个操作数,和成员访问有相同的属性。

优先级第三/四高: 接下来是双目运算符, 乘/除/取余(优先级三) 比 加减(优先级四)优先级高,符合直觉。

优先级第五/六高: 算术运算比关系运算优先级高。 加减乘除移位比所有比较符号优先级高。移位这种算术运算 >>, << 比 >,< >=,<= 的符号优先级高。先算出来再比较。

优先级第六/七高: >=, <= 比 ==, !==优先级高。这个没有什么规则。就是C语言编译器的人自己定的规则。 先比较大小,再比较是否相等。

优先级第八/九/⑩高: 位运算自己的比较,依次是 & ^ |

优先级第十一/十二: 逻辑运算 && ||

第十三: 孤零零的一个 三目。

第十四:各种赋值

第十五:孤独的逗号。

所以,总结起来规则是:

  1. 成员运算优先级最高。成员运算符算是单目的一种,所以剩下的单目运算符次高。(单目优先原则,直觉)
  2. 算术运算符比关系比较运算符高。(先算[包括移位]出来再比较,直觉)
  3. 关系比较运算符比位运算符高。(比较完再来做位运算,非直觉)
  4. 位运算比逻辑运算符高。(有运算结果再来做逻辑与或)
  5. 逻辑运算比赋值运算符高。(做完算术,比较,位和逻辑运算再来赋值)
  6. 逗比排在最后。
  7. 结合规则完全从直觉去判断。

Lockless stack

The core logic of lockless programming is the CAS operation brought by processor. CAS can compare and exchange value between two number in a single instruction. From the perspective of CPU synchronization primitive, CAS instruction like “cmpxchg” is expensive. but from the view of application programmer, the lockfree data structure is really attractive for its free user from using more expensive operation system “mutex locks” which will switch the worker thread from background back and forth.

LockFree data-structure always comes with ABA problem, according herb sutter. for example, We need to resolve the deletion issue: we can’t delete a node if a concurrent reader/writer might be pointing to it.

there are 4 options to solve the problem:

  • Option1: Use lazy garbage collection. solve the problem because memory cannot be reused while any pointers to it exist, However, destruction of nodes becomes nondeterministic.
  • Option2: Use reference counting ( garbage collection). solve the problem in cases without cycles.
  • Option3: Never actually delete a node ( only logically delete) , can work when deleting is rare.
  • Option4: Put auxiliary nodes in between actual nodes. Contains a next pointer only, no data. These links don’t move. Enables operations on adjacent nodes to run without interference.

STL Review

159436 share 352353.56 bonus 70470.71 tax 281882.85 actual

STL六大组件:1,容器。2,算法。3,迭代器。4,仿函。 5,适配器。6,分配器

顺序容器 Vector:

1,  分配的内存是以2的倍数动态增长。内存不够时,分配空间是当前的2倍。
    同时将原来的内容复制到新的空间。消耗较大。
    vector的allocator实现二级空间配置器,二级配置直接管理内存池, 伙伴系统,自由链表组成。
   
 
2,  连续存储空间,插入删除较慢。
3, 使用vector<int>.swap(vector_to_be_clear)来消除内存。
使vector_to_be_clear成为临时对象,释放内存。->小技巧。 
4, clear只会把vector的size清为零,不会释放内存。
5, vector是随机迭代器的顺序容器。
6, 随机存取优势。插入劣势。
7, 和dque相比,删除元素不会释放空间,重新申请空间时有额外的元素拷贝操作。
8,  access方法: [] , at,  front, back, data
    modifier方法: 
     assign, 
     push_back(), 
     pop_back(), 
     insert, 
     erase, 
     swap, 
     clear, 
     emplace, 
     emplace_back()

    capacity方法: 
     size, 
     max_size, 
     resize,  
     capacity, 
     empty, 
     reserve, 
     shrink_to_fit 

顺序容器 Deque:

1, 支持随机访问迭代器。
2, deque的内存空间分布是小片的连续,小片间用链表相连。
3, deque的重新分配要比vector快。重新分配后原有元素不需要拷贝。
4, 随机存取优势。插入劣势。 
5, 和vector相比,删除元素释放空间,重新申请空间没有额外元素拷贝操作。
6,  access方法: 
      [],
      at, 
      front, 
      back

    modifier方法: 
      assign, 
      push_back(), 
      push_front(), 
      pop_back(), 
      pop_front(), 
      insert, 
      erase,
      swap,  
      clear,  
      emplace, 
      emplace_front, 
      emplace_back

顺序容器 List:

1, 双向链表,内存空间不连续。
2, List支持双向迭代器。
3, 很好的支持任意地方插入和删除。
4, 频繁插入优势。存取劣势。
5, 方法:
      push_back(element)
      pop_back()  

关联容器 Map:

1,  map不保存数据时,每个节点占用 2个指针+1个枚举的空间。 
2, map是一个有序的key排列,提供双向迭代器。(非严格意义的平衡二叉树)。 
3, 插入相同的key会返回不成功。如果想支持使用multimap以支持1:N的存储能力。 
4, access: [], at     
    modifier: insert, erase, swap, clear, emplace,     emplace_hint     
    observer: key_comp, value_comp     
    operation:  find, count, lower_bound, upper_bound, equal_range     
    allocator: get_allocator

关联容器 unordered_map

 1, 方法buckets:
     bucket_count
     max_bucket_count
     bucket_size
     bucket

     Hash策略:
     load_factor
     max_load_facotr
     rehash
     reserve

     Observer:
     hash_function
     key_eq
     get_allocator

     Modifier:
     emplace
     emplace_hint
     insert
     erase
     clear
     swap

     元素查找:
     find
     count
     equal_range

     元素访问:
     at
     []

关联容器 Set:

1, 底层使用红黑树,同map
2, 默认对元素进行升序排列。
3, 不能直接修改key。需要先删除后插入。
4,  方法上就是红黑树的抽象方法,同map。
    capacity: empty, size, max_size
    modifier: insert, erase, swap, clear, emplace, emplace_hint
    observer: key_comp, value_comp
    operation: find, count, lower_bound, upper_bound, equal_range
    allocator: get_allocator

容器适配器 queue

1, 以标准STL容器deque为基础。
2, FIFO功能。
3,  方法: empty, size, front, back, push, pop, emplace,swap

容器适配器 stack

1, 以标准STL容器deque为基础。
2,FILO功能。
3, 方法: empty, size, top, push, emplace, pop, swap

顺序容器 array

1, 定长,定类型。
2, capacity方法: size, max_size(),empty
    access方法: [], at, front, back, data
    modifier:  fill, swap

mmap on MacOS

let’s see a snippet of code, Guess what happened on MacOS ?

 size_t pageSize = getpagesize();

  std::cout << " page size is " << pageSize << std::endl;

  int handle = shm_open("mempool_tag", O_CREAT | O_RDWR , 0777 );

  if (handle) {
     std::cout << std::setw(20) << "shm_open yield with  " << handle << std::endl;
  }
  else {
     ftruncate(handle, GetPoolSize());  
  }
  
  ftruncate(handle, GetPoolSize());  

  char* region = (char*) mmap( NULL, 
                               GetPoolSize(),
                               PROT_READ | PROT_WRITE | PROT_EXEC,
                               MAP_ANONYMOUS,
                               handle,
                               GetPoolSize()*20);
  if (region == MAP_FAILED) {
     return -1; 
  }

  return 0;

NerdTree hacks

I came with the problem when exit from the vim with “:q” command.

NERDTree still display after :q command

and this seems the buffer window counter problem.

fix by:

function! CheckLeftBuffers()
  if tabpagenr('$') == 1
    let i = 1
    while i <= winnr('$')
      if getbufvar(winbufnr(i), '&buftype') == 'help' ||
          \ getbufvar(winbufnr(i), '&buftype') == 'quickfix' ||
          \ exists('t:NERDTreeBufName') &&
          \   bufname(winbufnr(i)) == t:NERDTreeBufName ||
          \ bufname(winbufnr(i)) == '__Tag_List__'
        let i += 1
      else
        break
      endif
    endwhile
    if i == winnr('$') + 1
      qall
    endif
    unlet i
  endif
endfunction
autocmd BufEnter * call CheckLeftBuffers()

https://yous.be/2014/11/30/automatically-quit-vim-if-actual-files-are-closed

Is Joinable reall joinable?

I found the semantics of joinable thread is really confusing.

if thread start working, the status use “joinable” to indicate the thread is working if joinable is false.

if thread stop working, we wish it exit gracefully, and call join() to tell the system thread has complete it’s destiny.

I just don’t quite understand that: why the author use the word “JOIN”

wordpress换血

使用nginx

使用上了大名鼎鼎的nginx,配置wordpress后,静态链接会失效,nginx和apache的重定向配置不同。配置文件如下:

server {
    listen         80 default_server;
    listen         [::]:80 default_server;
    server_name    arthurcode.com www.arthurcode.com;
    root           /foo/bar/arthurcode;
    index          index.php index.html;

  location ~* \.php$ {
    fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
    include         fastcgi_params;
    fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
    fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
  }

  location = /favicon.ico { log_not_found off; access_log off; }
  location = /robots.txt { log_not_found off; access_log off; allow all; }
  location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
        expires max;
        log_not_found off;
  }
    
  location / {
        #try_files $uri $uri/ =404;
        try_files $uri $uri/ /index.php$is_args$args;
  }

}

wordpress 手动更新

  1. 备份 WordPress 网站文件及数据库;(用phpadmin导出的sql文件是可编辑的,部分不能导入的SQL语句可删除)。
  2. 下载最新版的 WordPress,切记进入WordPress 官网下载 zip 程序包,并用md5校验。
  3. wp-content文件夹和wp-config.php文件替换最新版本中对应wp-content和wp-config.php。
  4. 最后,在浏览器中访问http://www.domain_name.com/wp-admin/upgrade.php
  5. 中国区会出现很多327 too many request 错误。根据观察可能是太多的重定向导致。

本来以为wp这么流行的系统不会碰到很多问题,实际上并非如此。。加了cache系统速度也没太快,可能是优化不够到位。严重怀疑wordpress各种xhr请求影响了速度。。

有点想去折腾简单的静态页面。(我这性格也不想暂时放弃,这浪费时间的问题下次靠脚本来解决问题。

现代C++的引用操作

先要理解什么是C++的值

cppreference对Value做了解释

C++11之前,lvalue,rvalue可以简单的理解为等号两边的值类型。但C++11后,语言对用户开放了更多的辅助类型以帮助设计者开发更好用的STL库。比较典型的就是引入glvalue(general left value), xvlaue( eXpiring value, for it is temporary value at most of time, it explained in stroustroup’s article), prvalue( pure right value).

lvalue:    有identity/address (这里的identity是指可以用来和其它值用来比较的一个addressable address。不能比较的表达式意味着没有Identity), 但是这个不能被move,转移它所拥有的值或指针。通常情况下见到的变量都是lvalue。

xvalue; 既有identity,又可以被move。比如函数的返回值。

prvalue: 没有identity,但可以被move。比如某个常量值。数字1,2,常量字符串 “rock”.

StackOverFlow有一个不错的图来解释左值和右值。

Taxonomy
C++11引入的move语改变传统lvalue与rvalue概念

C++标准进一步解释了xvalue

判断一个表达式是xvalue的依据:

  • 无论是显式还是隐式的函数调用,其返回值是对象类型的右值引用(rvalue reference to object type)
  • 将右值引用强制转换(cast)为一个 对象类型。
  • 非静态非引用类型数据成员访问表达式。如 func().m; a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue)
  • 指向成员的指针表达式,如*pbm。pb是xvalue。 (a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.)

统一来讲,带名字的右值引用被当做左值,没名字的右值引用被当做xvalue;而如果引用的对象是函数的话,则不管有无名字都是左值。(In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether namedornot)

And as a follow up few examples are given. Here’s an extension to them:

struct A { int m; };

A&& operator+(A, A);
A a;
A b;
a + b;   // xvalue

A&& f();
f();     // xvalue
f().m;   // xvalue

A a;
static_cast<A&&>(a);  // xvalue
std::move(a);         // xvalue

It’s a good “Move”

move本身不对其中的值进行操作,它是配合STL模板函数来完成将A容器的引用指针数据直接转交给B容器。如:

std::vector A(2,3);
std::vector B(std::move(A));

转移发生在B的右值拷贝构造函数中: std::vector(std::vector&& a){ this.data_pointer = a.data_pointer; a.reset();}

xvalue的存在就像薛定谔的猫,在某些场景下,xvalue既可以左又可以右。(这里会列出更多场景)

forward仅仅是为了在参数传递过程中,保持&&类型或&类型而引入。毕竟我们要区分到底是一个 1,还是一个int i=1。

B.S对lvaue,rvalue的解释,我认为对一般人来说不好理解。特别在解释move的解释上偏晦涩。 在编程实现上,std::move/std::forward 这对兄弟可以用来理解URef,RRef,LRef之间的关系。

// move实现 
template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }


// forward实现一个rvalue的类型传递
 template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
		    " substituting _Tp is an lvalue reference type");
      return static_cast<_Tp&&>(__t);
    }

// forward实现一个lvalue的类型传递
  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }



类型推导

参数在推导传递过程中,允许退化(decay, reference collapse)。退化的场景有如下:

T&& -> T&
T&& & -> T&
T& && -> T&
T&& && -> T&&

因此当Foo(&)传入一个&&类型时,我们为了保持右值特性,使用forward保持。在Foo(&&)传入一个&类型时,退化为左值类型T&,也使用forward保持。

Scott Meryer 对这个问题展开了更有意思的解释

他引入一个新的概念来解释这个令人费解的特性:URef (universal reference),把URef比作随着温度而决定性别的火鸡。

  • T&& , auto&&, typedef T&&, decltype(T&&) T是一个符号,帮助类型推导。我们称它“通用引用”。 它既可以Bind一个左值(lvalue)后成为一个左值引用(LRef),也可以Bind一个右值(rvalue)成为一个右值引用(RRef)。[注意是 T]
  • 那你说, vector::push_back(T&&)是一个URef吗? 不,vector的这个函数已在编译时实例化时确定了T,如vector::push_back(Widget&&), 所以是rvalue引用。

来看几个例子:

/*auto deduction example*/
vector<int> aVector(6);    //aVectorp[5] 是一个典型的lvalue
auto&& aRef = aVector[5];  //推导为vector<int>&, URef绑定后成为LRef。
auto&  bRef = aVector[5];  //推导为vector<int>&, LRef绑定LRef。
auto&& cRef = std::move(aVector); //推导为vector<int>&&, URef绑定rvalue成为RRef

int&&  rri = 5;          // 无推导,类型为int&&
auto   autoRR = rri;     // 推导为int, 即使rri是const int&& ,去掉const和&&
auto&  autoRRR = rri;    // 推导为int&
auto&& autoRRRR = rri;   // 推导为int&
auto&& autoRRRRRR = autoRRR; //推导为int&
auto   autoX = autoRRRRRR;   //推导为int,即使autoRRRRRR是const int&&。
auto&& autoY = rri;      // 按照collapse规则,应该&&,但是这里推导为&
                         // 按照Scott的解释,rri初始化URef已经成为一个LRef
/*decltype example*/
Widget w;
decltype(w)&& r1 = std::move(w); // r1 type is Widget&&
decltype((w))&& r2 = std::move(w); // (w) is a expression,return &, r2 is Widget&

总结起来为以下:

  • type&& 并不等价于 右值引用(rvalue reference)
  • type&& 用于 语法 + 类型推导,我们称之为通用引用(URef, Universal Reference.)
  • URef最后是LRef还是RRef取决于初始化。LValue=>LRef, RValue =>RRef.
  • 并不是所有模板的T&&是URef。比如Vector::push_back的&&参数版本。
  • 重载URef通常是不对的。行为不是你所预期。
  • 类型推导和引用退化(reference collapsing)是需要URef的基本场景:模板,auto,typedef, decltype。

ROS的设计

    这篇文章主要讲ROS的几个核心设计:

0,ROS的实时性设计

1, ROS如何做消息的多类型适配。(MessageFormat,MessageAdapter)

2, C++的模板和智能指针在ROS消息模块的应用。(shared_ptr)

3, 如何通过去除序列化和反序列化来增强ROS的实时性。(自定义allocator)

ROS的实时性改造

事实上,ROS1.0实时性存在很大问题。主要来源于:1,内存的频繁动态申请,包括STL。2,事件通知(condition_variable,mutex)引起的线程切换。

序列化本身只是memcpy和对应类型的赋值操作,其时间是可以有上界的。为了让用户编程方便,内部会有较多的临时堆内存,内存申请引发的page fault会是一个让车载应用不放心的地方。

另外linux本身的cfs调度是一个动态调度机制,它虽然试图给每个应用程序均分CPU时间片。但因为系统负载和应用程序之间本身的逻辑(比如通知,阻塞,抢占,同步)会导致完成一个具体的任务(比如通信任务)时间上界不可知。在不同场景下处理同一个任务的时间抖动范围也不是平稳的。

如果要确保一个Safety-Critical的应用,要从应用层面和中间件层面以及操作系统层面上都要满足时延的可预测能力。具体要解决的问题就是:内存要静态申请,阻塞任务要有超时机制,调度的时间片要完全可控。这些问题也是要求,适用到所有软件层面。

关于deterministic real-time system在车载上的应用,TTTech有篇很好的文章,详细介绍了TTTech的实现方法,值得进一步挖掘:

http://cs.uni-salzburg.at/~scraciunas/pdf/techreports/mclean_DTU19.pdf

消息的多类型适配

         ROS提供基本类型( uint8_t, uint16_t, uint32_t, double ,float)外,还提供duration,Time等自定义的定长类型。此外Header,String,Array这种变长类型的消息。

       为什么需要typename,因为在引用时,编译器需要知道class::typename 到底是嵌套类型,还是静态函数或成员变量。

       Serializer<T>::write/read接口的设计把模板的能力发挥的恰到好处,对每个基础类型的数据implement一个对应的序列化(write)/反序列化(read)操作函数。符合类型根据类型T,去找到对应的函数模板类型去Ser/deSer。

      大多数的库包括Adaptive AutoSar,DDS都是按照类似的思路完成这种操作。C++的STL编程模式在处理Serialization/DeSerialization这类功能时特别常见。

     ROS的*.msg生成的头文件中带有该类型的序列化/反序列化入口。该入口将调用serialization<T>中的函数对每个成员做序列化/反序列化。

     在回调的时候,为了适配多个参数类型,也带有Message Adapter这个类。

 

ROS中智能指针的使用

ROS中使用了weak_ptr,unique_ptr,shared_ptr, 其中shared_ptr在进程内通信的publish接口中使用了。进程内通信利用shared_ptr可以有效减少多线程数据的申请释放问题带来的bug。