问题
In a project of mine, I have an ID generator for types that looks similar to this:
class Family {
static std::size_t identifier;
template<typename...>
static std::size_t family() {
static const std::size_t value = identifier++;
return value;
}
public:
template<typename... Type>
inline static std::size_t type() {
return family<std::decay_t<Type>...>();
}
};
std::size_t Family::identifier{};
Usage:
const auto id = Family::type<FooBar>();
It works just fine for my purposes, but it has some limitations. The most annoying one (purpose of the question) is that it fails when used by an executable that links to shared libraries if all of them try to create identifiers. The result is usually that the n-th identifier is assigned to different types across boundaries because each shared library maintains their own separate Family::identifier
.
Some shared library guys pointed out that a more reliable solution would be appreciated, but failed to suggest one that didn't ruin the performance (almost all of them introduce containers, find functionalities and memory allocation).
Is there any alternative approach that works around the aforementioned limitations without losing the performance of the current design?
I searched through SO and found some interesting answers. Many of which were several years old. I'd like to explore solutions up to the latest revision of the standard instead, as long as the interface of the existing class remains intact.
This one is the most interesting one. It uses addresses of static members to achieve the same, but it doesn't fit with the idea of sequentially generated identifiers
Note: using RTTI isn't an option unfortunately.
Note : ids must be generated sequentially and starting from 0 as in the solution presented above.
回答1:
Your problem occurs because you have this line in your header file:
std::size_t Family::identifier{};
It therefore ends up in each translation unit. Instead, you need to move the storage for this to a .cpp source file which is compiled only once, perhaps into a shared library of its own. Then there will be just one instance of identifier
in a program, and it will work as you intend.
You could also move identifier
from being a class static
variable to a global extern
one in the header file (and as above, define it in a single .cpp file).
If you have C++17 or later, you can also try:
inline std::size_t Family::identifier{};
While the language does not guarantee (or even mention) what happens when you use this new feature across shared libraries boundaries, it does work on my machine.
回答2:
If you do not require ids to be sequential integers, you can use an address of a static member of a template as an id. The benefit of this approach is that it does not require any run-time initialization (uses static initialization):
// in a header
class Family {
template<class...> struct Id { static char const id; };
template<typename... T>
static std::size_t family() {
return reinterpret_cast<std::size_t>(&Id<T...>::id);
}
public:
template<typename... Type>
static std::size_t type() {
return family<std::decay_t<Type>...>();
}
};
// in a header
template<class... T>
char const Family::Id<T...>::id = {};
// elsewhere
int main() {
auto int_id = Family::type<int>();
auto int_int_id = Family::type<int, int>();
}
You can also make that id
a compile time constant and use it as a template argument:
// in a header
struct Family {
template<class...> struct Id { static char const id; };
};
// in a header
template<class... T>
char const Family::Id<T...>::id = {};
// elsewhere
template<char const*>
struct X {};
int main() {
X<&Family::Id<int>::id> x;
}
回答3:
if you don't care about sequential IDs, use the address of the function as identifier.
template<typename... T>
uintptr_t getID() {
return reinterpret_cast<uintptr_t>(&getID<T...>);
}
and then
auto intID = getID<int>();
auto floatID = getID<float>();
...
来源:https://stackoverflow.com/questions/51332851/alternative-id-generators-for-types