orocos 类型系统分析

其实代码分析是完整的,需要点耐心才能看完。。。

在 orocos 中所有类型的信息是通过一个叫 TypeInfo 的结构体来保存的,每增加一种类型就会 new 一个 TypeInfo 对象来表示该类型的信息:

/**
 * A class for representing a user type, and which can build
 * instances of that type. Once you get hold of a TypeInfo
 * object pointer, this pointer will be valid during the whole
 * lifetime of the process. 
 */
class RTT_API TypeInfo
{
public:
    typedef const std::type_info * TypeId;

    TypeInfo(const std::string& name) : mtypenames(1,name) {}  // 用户自定义类型的名字

    ~TypeInfo();

    const std::string& getTypeName() const { return mtypenames[0]; }

    std::vector<std::string> getTypeNames() const;  // 返回 mtypenames

    void addAlias(const std::string& alias){  // 类型别名
    // only alias new names:
    if ( !alias.empty() && find(mtypenames.begin(), mtypenames.end(), alias) == mtypenames.end() )
        mtypenames.push_back(alias);
     }

    // Returns the compiler generated type id pointer.
    TypeId getTypeId() const { return mtid; }  // 获得编译器生成的 const std::type_info *


    // Returns the compiler generated type name (non portable accross compilers!).
    const char * getTypeIdName() const { return mtid_name; }


    void setTypeId(TypeId tid) { // 由于TypeInfo类与类型无关,所以需要显示的setTypeId
        mtid = tid;
        mtid_name = tid->name();
    }

    ...

    std::vector<std::string> mtypenames;  // 初始化为构造是时候传入的名字(用户自定义的名字), addAlias会push_back一个名字。
    const char* mtid_name;  // 这个是 std::type_info.name() 返回的字符串,编译器生成,根据平台不同。
    TypeId mtid;  // 这个是 const std::type_info * 类型
...
};

然后 orocos 中有一个单例的全局对象 TypeInfoRepository, 根据这个类的名字就可以知道该对象是一个包含所有类型信息的仓库

class RTT_API TypeInfoRepository
{
    TypeInfoRepository();
    typedef std::map<std::string, TypeInfo*> map_t;
    map_t data;  // 保存所有类型信息的map容器, 即仓库
    ...
}

在 orocos 中,用户自定义类型的注册过程是这样的:
1. 定义类型
2. 添加到全局对象中

e.g:

class UserType  // 用户定义类型
{}

TypeInfoRepository::Instance()->addType( new StdTypeInfo("UserTypeName"));  // 注册该类型&名字

其中 StdTypeInfo 派生至 TypeInfoGenerator,而TypeInfoGenerator 是 orocos 类型信息的基类,e.g:

StdTypeInfo : public ... : public TypeInfoGenerator
{}

在上面调用 addType 的时候,如下过程被调用:

bool TypeInfoRepository::addType(TypeInfoGenerator* t)
{
    if (!t)
        return false;
    std::string tname = t->getTypeName();
    TypeInfo* ti = t->getTypeInfoObject();  // 这个调用见下面分解

    {
        MutexLock lock(type_lock);
        if (ti && data.count(tname) && data[tname] != ti ) {
            log(Error) << "Refusing to add type information for '" << tname << "': the name is already in use by another type."<return false;
        }
    }
    // Check for first registration, or alias:
    if ( ti == 0 )
        ti = new TypeInfo(tname);  // 第一次注册的时候会 new
    else
        ti->addAlias(tname);

    if ( t->installTypeInfoObject( ti ) ) { // will return false, so this will not be deleted!
        delete t;
    }
    MutexLock lock(type_lock);
    // keep track of this type:
    data[ tname ] = ti;

    log(Debug) << "Registered Type '"<"' to the Orocos Type System."<<Logger::endl;
    for(Transports::iterator it = transports.begin(); it != transports.end(); ++it)
        if ( (*it)->registerTransport( tname, ti) )
            log(Info) << "Registered new '"<< (*it)->getTransportName()<<"' transport for " << tname <return true;
}

对上面代码进行分解,其中 :

TypeInfo* ti = t->getTypeInfoObject() 是虚函数调用,实际调用的是其父类下面这个(带类型信息的):

// 这个 T 就是上面的用户自定义类型
TypeInfo* getTypeInfoObject() const {
    return TypeInfoRepository::Instance()->getTypeInfo<T>();  // 这里又调用全局对象的 getTypeInfo
        }


然后调用下面这个,使用 typeid(T) 来获取其上面的类型 T 的 const std::type_info *:
TypeInfo* TypeInfoRepository::getTypeById(TypeInfo::TypeId type_id) const {  // TypeInfo::TypeId 就是 const std::type_info *
      if (!type_id)
          return 0;
      MutexLock lock(type_lock);
      // Ask each type for its type id name.
      map_t::const_iterator i = data.begin();
      for (; i != data.end(); ++i){
        if (i->second->getTypeId() && *(i->second->getTypeId()) == *type_id)  // 查看map中是否已经存在该类型信息,显然第一次addType的时候该map为空,所以返回下面的 0
          return i->second;
      }
      return 0;  // map中不存在,返回0
    }

再次回到最上面的 addType 函数调用中:

if ( ti == 0 )
    ti = new TypeInfo(tname);  // 这里生成一个用户自定义类型的TypeInfo对象。

if ( t->installTypeInfoObject( ti ) ) {  // 这个函数与其看起来的意思相反,反而是类似 ti->set(t) 这种效果。
    delete t;  // 哈,由于installTypeInfoObject永远返回false,所以 TypeInfoGenerator 也就不会被delete了(而是在内部变成了一个shared_ptr)。看起来怪怪的...
}


//在 t->installTypeInfoObject( ti ) 内部,不仅把 t 变成了一个shared_ptr
//而且同时还根据模板展开定义一个 internal::DataSourceTypeInfo 类,该类有一些静态函数可以调用,以获取针对类型 T 的信息:

bool installTypeInfoObject(TypeInfo* ti) {
    // Install the factories for primitive types
    ti->setValueFactory( this->getSharedPtr() );
if (use_ostream)
ti->setStreamFactory( this->getSharedPtr() );

    // Install the type info object for T
    internal::DataSourceTypeInfo<T>::value_type_info::TypeInfoObject = ti;  // 模板展开,定义与类型T相关的类 DataSourceTypeInfo
    ti->setTypeId( &typeid(T) );  // 构造的时候只有一个名字,这里再setTypeId,至此, ti 的信息就完整了。

    // Clean up reference to ourselves.
    mshared.reset();  // 解决t自身内部的shared_ptr,否则t永远也不会被销毁
    // Don't delete us, we're memory-managed.
    return false;  // 永远返回 false
}

前面介绍了这么多关于这个类型系统,但是如何使用这个类型系统呢?

这就得从 orocos 的数据表示开始了:

orocos 内部的所有数据都是派生自 DataSourceBase ,只要是使用orocos 内部数据,其基类必定是 DataSourceBase,例如 RTT::Property;该数据结构是类型无关的,且只定义了一些接口,其中就有 getTypeInfo() 这个接口:

  class RTT_API DataSourceBase
  {
  protected:
      // 这里有关于使用 boost::intrusive_ptr 而不是 shared_ptr 的解释
      /**
        *We keep the refcount ourselves.  We aren't using
        *boost::shared_ptr, because boost::intrusive_ptr is better,
        *exactly because it can be used with refcounts that are stored
        *in the class itself.  Advantages are that the shared_ptr's for
        *derived classes use the same refcount, which is of course very
        *much desired, and that refcounting happens in an efficient way,
        *which is also nice :)
      */

      mutable os::AtomicInt refcount;  // boost::intrusive_ptr 的引用计数

      /** the destructor is private.  You are not allowed to delete this
       * class RTT_API yourself, use a shared pointer !
       */
      virtual ~DataSourceBase();

  public:

      typedef boost::intrusive_ptr shared_ptr;

      ...

      virtual const types::TypeInfo* getTypeInfo() const = 0;  // 就是这个接口

DataSource 结构派生自 DataSourceBase,是有类型信息的:

template
class DataSource
  : public base::DataSourceBase
{
protected:
    virtual ~DataSource();

public:

    ...

    virtual const types::TypeInfo* getTypeInfo() const  // override 基类 的 getTypeInfo,其中又调用下面这个 类 静态函数
    {
        return GetTypeInfo();
    }

    static const types::TypeInfo* GetTypeInfo();  // DataSource类的静态函数,见下文分析.

DataSource类的静态函数的实现如下:

template< typename T>
const types::TypeInfo* DataSource<T>::GetTypeInfo() { return DataSourceTypeInfo<T>::getTypeInfo(); }  // 由此可知,调用的DataSourceTypeInfo<T>静态对象的getTypeInfo()

这个全局静态对象(类型相关) DataSourceTypeInfo 是最开始的时候 installTypeInfoObject(ti) 内部生成的。

过程就是:

基类(类型无关) -> 派生类(类型相关) -> 派生类静态函数(类型相关) -> 全局DataSourceTypeInfo类的静态成员函数(类型相关) -> 单例对象的getTypeInfo(类型无关) -> 目标类型的TypeInfo对象指针

这里有个疑问,为什么需要这么设计?需要获得特定类型的TypeInfo对象指针也没那么复杂啊。。。难道为了解耦整个系统的类型注册这部分代码,为了新增加类型后还可以分模块编译(而不是所有代码放在一起才能编译链接)?

仔细想了一下,好像是这么回事。orocos中所有类型可以分为两种,第一种是在类型系统中注册过的类型,第二种是没有在类型系统中注册过的(废话。。。)。这两部分代码都是模板类,理论上需要共用同一套代码,但是怎样设计能够让这两部分看起来能够单独工作互不影响,但是又能够联系在一起呢?就是通过设计这些全局的模板类以及其静态成员(变量和函数)来实现。因为根据 ODR 规则,模板函数可以存在很多份定义,因为模板类是针对每一种类型T存在一份定义,而不是每一个cpp中实例化的时候存在一份定义,这也就是说不同模块的库文件在链接的时候(或者动态加载的时候)不会出现链接重定义的错误。see SO link:
>

The linker will remove all but one copy of each instantiation. But the compiler can’t know what other modules you will link with, so it has to compile whatever instantiations you use, unless you provide a promise to provide it at link time, using extern. Templates, like inline functions, are exempted from the multiple definitions conflict; the linker will just pick one copy to use. The ODR rule still applies, all copies with the same name must have identical behavior, so that which one the linker picks doesn’t matter.

通过 DataSourceTypeInfo 这个桥梁, DataSourceTypeInfoRepository 就能单独编译并加载,并且动态库加载后就能够找到唯一一份DataSourceTypeInfo的实现。

更新,见链接测试: https://github.com/fengzhuye/cpp-learn/tree/master/dynamic_load_with_template

如果将 class 的 visibility 定义为 “hidden”,或者编译可执行程序的时候没有加上-rdynamic则可执行程序和动态链接库中都自己保存有一份 template class static member,这个 static member 不是全局共享的,当前模块中。但是如果 visibility 为 public(默认) 并且编译可执行程序的时候加上了 -rdynamic 选项,则这个 static member是全局共享的(动态加载的时候两个模块内的这个对象会链接到同一个地址上)。

例如定义如下模板类:

#define TEST_API __attribute__((visibility("hidden")))

template<class T>
struct TEST_API Blast
{
    static double b;
    static double getB()
    {
        return b;
    }
};

在动态库 liba.so 中赋值 Blast::b = 1.23 ; 在动态库 libb.so 中赋值 Blast::b = 3.21 ; 那么在 liba.so 中调用 getB 获得的永远是 1.23, 在 libb.so 中调用 getB 获得的永远是 3.21,与调用顺序无关。但是如果这样定义:

#define TEST_API __attribute__((visibility("default")))

template<class T>
struct TEST_API Blast
{
    static double b;
    static double getB()
    {
        return b;
    }
};

main程序加载动态库,并且编译main程序的时候加上 -rdynamic 则 Blast::b 是所有模块全局共享的。


所以,归根结底,基类是通过一堆不同类型全局对象的静态成员函数来获取其父类类型信息的 TypeInfo 对象的。获得了TypeInfo对象就能够调用针对该特定类型的一些操作(例如,实现针对 该类型 的 cast,串行化,序列化等等操作)。


大概过程总结就是:
先创建用户自定义类型,并将该类型信息添加到全局单例对象TypeInfoRepository 中。 在代码运行过程中,如果我获得了一个数据的基类指针(类型无关的DataSourceBase),我需要调用虚函数getTypeInfo来获得其父类(类型相关)的TypeInfo*对象,而其父类通过将getTypeInfo绑定到一个特定类型的全局的静态对象上,该全局静态对象再调用单例对象TypeInfoRepository的方法来获得特定TypeInfo* 的信息。

因为系统中每一个类型 T 都对应一个全局类定义 DataSourceTypeInfo,所以存在很多个针对不同类型 DataSourceTypeInfo 类定义,这些不同的 DataSourceTypeInfo 里头全是静态成员和静态成员函数,有一个静态成员就保存着有一份与该类型相关的TypeInfo对象的指针。至于为什么需要用到全局类DataSourceTypeInfo,这就是orocos类型系统插件形式的强大之处了。

获得了对应的对象的TypeInfo 后,就可以调用针对该对象的类型的一些操作,例如,static_cast,ostream输出, 文本化,串行化,序列化等等,这个是关键。

这个设计模式很强大,关于插件机制的架构,可以google Boost.DLL

see ref:
http://en.cppreference.com/w/cpp/types/type_info/hash_code
https://en.wikipedia.org/wiki/One_Definition_Rule
http://www.boost.org/doc/libs/1_64_0/doc/html/boost_dll/tutorial.html
https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam

你可能感兴趣的:(orocos)