C++ 安全且跨越库边界的唯一类类型 ID
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/922442/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Unique class type Id that is safe and holds across library boundaries
提问by xeon
I would appreciate any help as C++ is not my primary language.
我将不胜感激,因为 C++ 不是我的主要语言。
I have a template class that is derived in multiple libraries. I am trying to figure out a way to uniquely assign an id int to each derived class. I need to be able to do it from a static method though, ie.
我有一个派生在多个库中的模板类。我试图找出一种方法来为每个派生类唯一分配一个 id int。不过,我需要能够从静态方法中做到这一点,即。
template < class DERIVED >
class Foo
{
public:
static int s_id()
{
// return id unique for DERIVED
}
// ...
};
谢谢!采纳答案by xeon
Here's what I ended up doing. If you have any feedback (pros, cons) please let me know.
这就是我最终做的。如果您有任何反馈(优点、缺点),请告诉我。
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;
}
// ...
};
Essentially the id will be assigned after doing a string comparison rather than a pointer comparison. This is not ideal in terms of speed, but I made the id static const so it will only have to calculate once for each DERIVED.
本质上,id 将在进行字符串比较而不是指针比较后分配。这在速度方面并不理想,但我将 id 设为静态常量,因此它只需为每个 DERIVED 计算一次。
回答by Ben Voigt
This can be done with very little code:
这可以用很少的代码完成:
template < class DERIVED >
class Foo
{
public:
static int s_id()
{
return reinterpret_cast<int>(&s_id);
}
};
回答by Klaim
In the modern C++ (03 - assuming you're using a recent compiler like gcc) you can use the typeidkeyword to get a type_info object that provides basic type informations at least at runtime - that's a standard (and then cross-platform) feature.
在现代 C++(03 - 假设您使用的是最近的编译器,例如 gcc)中,您可以使用typeid关键字来获取至少在运行时提供基本类型信息的 type_info 对象 - 这是一个标准(然后是跨平台)功能.
I took the example from wikipedia and added a template/inheritance check, it seems to works well but i'm not certain for the int version (that is a hack exploiting the assumption that the compiler will have the types names somewhere in a read only memory space...that might be a wrong assumption).
我从维基百科中拿了这个例子并添加了一个模板/继承检查,它似乎工作得很好,但我不确定 int 版本(这是一个黑客利用的假设,即编译器将在只读的某处拥有类型名称内存空间......这可能是一个错误的假设)。
The string identifier seems far better for cross-platform identification, if you can use it in you case. It's not cross-compiler compatible as the name it gives you is "implementation defined" by the standard - as suggested in comments.
如果你可以在你的情况下使用它,字符串标识符似乎更适合跨平台识别。它不是交叉编译器兼容的,因为它给你的名字是标准的“实现定义”——正如评论中所建议的那样。
The full test application code:
完整的测试应用代码:
#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;
}
Outputs :
输出:
class Person
class Employee
class Person *
class Employee
class Test<int>
3462688
3462688
3462688
int
class Person
3421584
class Employee
3462504
float
3462872
This works at least on VC10Beta1 and VC9, should work on GCC. By the way, to use typeid (and dynamic_cast) you have to allow runtime type infos on your compiler. It should be on by default. On some plateform/compiler (I'm thinking about some embedded hardwares) RTTI is not turned on because it have a cost, so in some extreme cases you'll have to find a better solution.
这至少适用于 VC10Beta1 和 VC9,应该适用于 GCC。顺便说一句,要使用 typeid(和 dynamic_cast),您必须在编译器上允许运行时类型信息。默认情况下它应该是打开的。在某些平台/编译器(我正在考虑一些嵌入式硬件)上,RTTI 没有打开,因为它有成本,因此在某些极端情况下,您必须找到更好的解决方案。
回答by Jherico
In my previous company we did this by creating a macro which would take the class name as a parameter, create a local static with the unique id (based on class name) and then create an override of a virtual function declared in the base class that returned the static member. That way you can get the ID at runtime from any instance of the object hierarchy, similar to the 'getClass()' method in a java object, though much more primitive.
在我以前的公司中,我们通过创建一个宏来实现这一点,该宏将类名作为参数,创建一个具有唯一 id(基于类名)的本地静态,然后创建在基类中声明的虚函数的覆盖返回静态成员。这样您就可以在运行时从对象层次结构的任何实例中获取 ID,类似于 java 对象中的“getClass()”方法,但要原始得多。
回答by user6567423
#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());
}
回答by zyndor
The below snippet works in VS(2015) and Release builds:
以下代码段适用于 VS(2015) 和 Release 版本:
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
Also tried and tested on GCC v7.3 (Ubuntu 16.04) and LLVM v10.0.0 (Mac OS High Sierra).
还在 GCC v7.3 (Ubuntu 16.04) 和 LLVM v10.0.0 (Mac OS High Sierra) 上进行了尝试和测试。
How it works: each instantiation of the TypeId<>
template gets its own, unique sDummy instance with its own, unique address. To be honest I'm not entirely sure why the function-static version didn't work in release -- I suspect identical comdat folding and optimizations.
它是如何工作的:TypeId<>
模板的每个实例都有自己的、唯一的 sDummy 实例和它自己的、唯一的地址。老实说,我不完全确定为什么函数静态版本在发布时不起作用——我怀疑相同的 comdat 折叠和优化。
Exercise for the reader: at least const and ref types should get the same type ID as the raw type.
读者练习:至少 const 和 ref 类型应该获得与原始类型相同的类型 ID。
回答by Todd Gardner
What kind of ID? Are you looking for an atomically increasing int? If a string is fine, what about:
什么样的身?你在寻找一个原子递增的 int 吗?如果字符串没问题,那么:
static string s_id()
{
return typeid(Foo<DERIVED>).name();
}
If it needs to be an int, but not automatically increasing, you could hash that for a 128-bit integer unlikely to have collisions (though likely a larger number than you need)
如果它需要是一个整数,但不会自动增加,你可以将它散列成一个不太可能发生冲突的 128 位整数(尽管可能比你需要的数字大)
回答by Alex
I'm not 100% happy with the answers so far and I have worked out one solution for me. The idea is to compute a hash of the type name using typeinfo. Everything is done once when loading the application so there's no runtime overload. This solution will work also using shared libraries as the type name and the hash of it will be consistent.
到目前为止,我对答案并不是 100% 满意,我已经为我制定了一个解决方案。这个想法是使用 typeinfo 计算类型名称的哈希。加载应用程序时一切都完成一次,因此没有运行时过载。此解决方案也可以使用共享库作为类型名称,并且它的哈希值将保持一致。
This is the code I use. This works great for me.
这是我使用的代码。这对我很有用。
#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;
}
回答by James Curran
There is nothing standardized. Further, there's no hack that I've found that's foolproof.
没有什么标准化的。此外,我发现没有任何黑客是万无一失的。
Best I've been able to come up with:
我能想出的最好的:
template < class DERIVED, int sid >
class Foo
{
public:
static int s_id()
{
return sid;
}
};
Foo<MyClass, 123456> derivedObject;
回答by Skurmedel
You can do the following:
您可以执行以下操作:
#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;
}
I don't know if it's a good idea though. The blah<>
part is a bit unintuitive too. Maybe you're better off assigning them ids manually, or you can create a base type, give it a template argument with your class id.
我不知道这是否是个好主意。这blah<>
部分也有点不直观。也许你最好手动为它们分配 id,或者你可以创建一个基本类型,给它一个带有你的类 id 的模板参数。