【问题标题】:Unique class type Id that is safe and holds across library boundaries安全且跨越库边界的唯一类类型 ID
【发布时间】:2010-10-29 16:14:22
【问题描述】:

感谢任何帮助,因为 C++ 不是我的主要语言。

我有一个在多个库中派生的模板类。我试图找出一种方法来为每个派生类唯一地分配一个 id int。不过,我需要能够通过静态方法来做到这一点,即。


template < class DERIVED >
class Foo
{
public:
    static int s_id()
    {
        // return id unique for DERIVED
    }
    // ...
};
谢谢!

【问题讨论】:

  • 您是否需要每个的唯一ID?还是每个object

标签: c++ class templates unique


【解决方案1】:

这可以用很少的代码完成:

template < class DERIVED >
class Foo
{
public:
    static int s_id()
    {
        return reinterpret_cast<int>(&s_id);
    }
};

【讨论】:

  • 指针不保证适合 int。考虑可选类型 uintptr_t 或一些实现定义的足够大小的整数类型。
  • @LarsViklund:没有必要保存整个指针值。除非您的代码超过 4GB,否则函数地址的低 32 位将是唯一的。
  • 刚刚在 Visual Studio 2015 中尝试过,虽然它在 Debug 模式下的工作方式非常棒,但由于优化,所有实现此技巧的类在 Release 模式下都返回完全相同的 id。
  • 基本上,相同的函数折叠是一个好主意,但是如果函数的地址被带到任何地方,优化器应该禁用它。 Microsoft 链接器不遵守该规则。
  • @proski:我认为你误解了这个问题——这提供了每个类型的唯一 ID,而不是每个实例。
【解决方案2】:

在现代 C++(03 - 假设您使用的是最新的编译器,如 gcc)中,您可以使用 typeid 关键字来获取至少在运行时提供基本类型信息的 type_info 对象 - 这是一个标准(然后是跨平台)功能

我从 wikipedia 中获取了示例并添加了模板/继承检查,它似乎运行良好,但我不确定 int 版本(这是一种黑客利用编译器将在某处具有类型名称的假设)一个只读的内存空间......这可能是一个错误的假设)。

字符串标识符对于跨平台识别似乎要好得多,如果你可以在你的情况下使用它的话。它与交叉编译器不兼容,因为它给您的名称是标准的“实现定义” - 正如 cmets 中所建议的那样。

完整的测试应用代码:

#include <iostream>
#include <typeinfo>  //for 'typeid' to work

class Person 
{
public:
   // ... Person members ...
   virtual ~Person() {}
};

class Employee : public Person 
{
   // ... Employee members ...
};

template< typename DERIVED >
class Test
{
public:
    static int s_id()
    {
        // return id unique for DERIVED
        // NOT SURE IT WILL BE REALLY UNIQUE FOR EACH CLASS!!
        static const int id = reinterpret_cast<int>(typeid( DERIVED ).name());
        return id;
    }

    static const char* s_name()
    {
        // return id unique for DERIVED
        // ALWAYS VALID BUT STRING, NOT INT - BUT VALID AND CROSS-PLATFORM/CROSS-VERSION COMPATBLE
        // AS FAR AS YOU KEEP THE CLASS NAME
        return typeid( DERIVED ).name();
    }
};

int wmain () 
{
    Person person;
    Employee employee;
    Person *ptr = &employee;



    std::cout << typeid(person).name() << std::endl;   // Person (statically known at compile-time)
    std::cout << typeid(employee).name() << std::endl; // Employee (statically known at compile-time)
    std::cout << typeid(ptr).name() << std::endl;      // Person * (statically known at compile-time)
    std::cout << typeid(*ptr).name() << std::endl;     // Employee (looked up dynamically at run-time
                                                    // because it is the dereference of a pointer to a polymorphic class)

    Test<int> test;
    std::cout << typeid(test).name() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_name() << std::endl;    

    Test< Person > test_person;
    std::cout << test_person.s_name() << std::endl;    
    std::cout << test_person.s_id() << std::endl;    

    Test< Employee > test_employee;
    std::cout << test_employee.s_name() << std::endl;    
    std::cout << test_employee.s_id() << std::endl;    

    Test< float > test_float;
    std::cout << test_float.s_name() << std::endl;    
    std::cout << test_float.s_id() << std::endl;    


    std::cin.ignore();
    return 0;
}

输出:

class Person
class Employee
class Person *
class Employee
class Test<int>
3462688
3462688
3462688
int
class Person
3421584
class Employee
3462504
float
3462872

这至少适用于 VC10Beta1 和 VC9,应该适用于 GCC。顺便说一句,要使用 typeid(和 dynamic_cast),您必须在编译器上允许运行时类型信息。它应该默认打开。在某些平台/编译器上(我正在考虑一些嵌入式硬件)RTTI 没有打开,因为它有成本,所以在某些极端情况下,您必须找到更好的解决方案。

【讨论】:

  • 我喜欢指针的reinterpret_cast;没想到。它对每个类都应该是唯一的(从技术上讲,重新解释转换不保证整数应该是分开的,但保证可逆性,这在逻辑上意味着唯一性)但是,字符串可能不是跨平台的;它给你的名字是标准的“实现定义”,我使用了平台,它把错误的名字交还给你。
  • 这是非标准且不安全的。根据 C++ 标准,不同类的type_info 可以有相同的name。 “智能”编译可以很容易地决定将那些相同的 C 字符串合并到同一个实例中(因此具有相同的地址)以尝试优化。
  • @logicor 确实如此。它们目前不是,但这不是一个完美的解决方案。哇,这个问题/答案是旧的 XD
【解决方案3】:

在我以前的公司中,我们通过创建一个宏来做到这一点,该宏将类名作为参数,创建一个具有唯一 ID(基于类名)的本地静态变量,然后创建一个在返回静态成员的基类。这样,您可以在运行时从对象层次结构的任何实例中获取 ID,类似于 java 对象中的 'getClass()' 方法,但更原始。

【讨论】:

  • 这听起来是一个很好的解决方案,但我对宏不是很熟悉。
【解决方案4】:

到目前为止,我对答案并不是 100% 满意,我已经为我制定了一种解决方案。这个想法是使用 typeinfo 计算类型名称的哈希值。加载应用程序时一切都完成一次,因此没有运行时过载。此解决方案也可以使用共享库作为类型名称,并且它的哈希值将保持一致。

这是我使用的代码。这对我很有用。

#include <string>
#include <typeinfo>
#include <stdint.h>

//###########################################################################
// Hash
//###########################################################################
#if __SIZEOF_POINTER__==8
inline uint64_t hash(const char *data, uint64_t len) {
    uint64_t result = 14695981039346656037ul;
    for (uint64_t index = 0; index < len; ++index)
    {
        result ^= (uint64_t)data[index];
        result *= 1099511628211ul;
    }
    return result;
}
#else
inline uint32_t hash(const char *data, uint32_t len) {
    uint32_t result = 2166136261u;
    for (uint32_t index = 0; index < len; ++index)
    {
        result ^= (uint32_t)data[index];
        result *= 16777619u;
    }
    return result;
}
#endif
inline size_t hash(const std::string & str) { return hash(str.c_str(), str.length()); }

//###########################################################################
// TypeId
//###########################################################################
typedef size_t TypeId;

template<typename T>
static const std::string & typeName() {
    static const std::string tName( typeid(T).name() );
    return tName;
}
template<typename T>
static TypeId typeId() {
    static const TypeId tId = hash( typeName<T>() );
    return tId;
}

【讨论】:

    【解决方案5】:
    #include <stdint.h>
    #include <stdio.h>
    
    #define DEFINE_CLASS(class_name) \
        class class_name { \
        public: \
            virtual uint32_t getID() { return hash(#class_name); } \
    
    // djb2 hashing algorithm
    uint32_t hash(const char *str)
    {
        unsigned long hash = 5381;
        int c;
    
        while ((c = *str++))
            hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
    
        return hash;
    }
    
    DEFINE_CLASS(parentClass)
    
        parentClass() {};
        ~parentClass() {};
    };
    
    DEFINE_CLASS(derivedClass : public parentClass)
    
        derivedClass() : parentClass() {};
        ~derivedClass() {};
    };
    
    int main() {
        parentClass parent;
        derivedClass derived;
        printf("parent id: %x\nderived id: %x\n", parent.getID(), derived.getID());
    }
    

    【讨论】:

      【解决方案6】:

      什么样的身份证?你在寻找一个原子增加的 int 吗?如果字符串没问题,那又如何:

      static string s_id()
      {
         return typeid(Foo<DERIVED>).name();
      }
      

      如果它需要是一个 int,但不是自动增加,您可以将其散列为一个不太可能发生冲突的 128 位整数(尽管可能比您需要的更大)

      【讨论】:

      • 啊,在写我的之前没有看到你的答案... +1 速度。
      【解决方案7】:

      这就是我最终要做的。如果您有任何反馈(优点、缺点),请告诉我。

      
      template < class DERIVED >
      class Foo
      {
      public:
          static const char* name(); // Derived classes will implement, simply
      // returning their class name static int s_id() { static const int id = Id_factory::get_instance()->get_id(name()); return id; } // ... };

      本质上,id 将在进行字符串比较而不是指针比较之后分配。这在速度方面并不理想,但我将 id 设为静态常量,因此它只需要为每个 DERIVED 计算一次。

      【讨论】:

        【解决方案8】:

        以下 sn-p 适用于 VS(2015) 和发布版本:

        template <typename T>
        struct TypeId
        {
          static size_t Get()
          {
            return reinterpret_cast<size_t>(&sDummy);
          }
        
        private:
          static char sDummy;
        };
        
        template <typename T>
        char TypeId<T>::sDummy; // don't care about value
        

        还在 GCC v7.3 (Ubuntu 16.04) 和 LLVM v10.0.0 (Mac OS High Sierra) 上进行了尝试和测试。

        它是如何工作的:TypeId&lt;&gt; 模板的每个实例化都有自己的、唯一的 sDummy 实例和自己的唯一地址。老实说,我不完全确定为什么函数静态版本在发布时不起作用——我怀疑相同的 comdat 折叠和优化。

        读者练习:至少 const 和 ref 类型应该获得与原始类型相同的类型 ID。

        【讨论】:

        • 刚刚测试过,效果很好!您介意解释一下为什么会这样吗?
        • @DavidColson 我试图解释工作原理(......不是太早了)。希望这会有所帮助。=)一旦我有了更清晰的想法,我会努力更新答案。
        【解决方案9】:

        没有什么标准化的。此外,我发现没有任何破解方法是万无一失的。

        我能想到的最好的:

        template < class DERIVED, int sid >
        class Foo
        {
            public:    
              static int s_id()    
              {        
                  return sid;
              }    
        };
        
        Foo<MyClass, 123456>   derivedObject;
        

        【讨论】:

          【解决方案10】:

          您可以执行以下操作:

          #include <iostream>
          
          
          template <int id = 5>
          class blah
          {
          public:
              static const int cid = id; 
          };
          
          int main(int argc, char *argv[])
          {
              std::cout << blah<>::cid << " " << blah<10>::cid << std::endl;
          
          }
          

          我不知道这是否是个好主意。 blah&lt;&gt; 部分也有点不直观。也许你最好手动为它们分配 id,或者你可以创建一个基类型,给它一个带有你的类 id 的模板参数。

          【讨论】:

            【解决方案11】:

            正如Paul Houx 指出的here,由于编译器优化,返回静态方法地址的技巧可能不起作用。但是我们可以使用 __FILE____LINE__ 宏 + volatile 关键字来解决问题。

            最终的解决方案如下所示:

            #define GET_TYPE_ID static size_t GetTypeId()   \
            {                                               \
                volatile const char* file = __FILE__;       \
                volatile uint32_t line = __LINE__;          \
                return (size_t)&GetTypeId;                  \
            }
            
            class ClassA
            {
            public:
                GET_TYPE_ID
            };
            
            class ClassB
            {
            public:
                GET_TYPE_ID
            };
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-04-16
              • 1970-01-01
              • 2013-02-26
              • 2018-09-11
              • 1970-01-01
              • 2012-06-02
              相关资源
              最近更新 更多