Jos*_*son 40 c++ templates rtti language-lawyer
我希望创建一个std::type_index不需要RTTI的替代方案:
template <typename T>
int* type_id() {
static int x;
return &x;
}
Run Code Online (Sandbox Code Playgroud)
请注意,局部变量的地址x用作类型ID,而不是其x自身的值.另外,我不打算在现实中使用裸指针.我刚刚删除了与我的问题无关的所有内容.在这里查看我的实际type_index实现.
这种方法听起来是否合理,如果是这样,为什么?如果没有,为什么不呢?我觉得我在这里不稳定,所以我对我的方法将会或不会起作用的确切原因感兴趣.
典型的用例可能是在运行时注册例程以通过单个接口处理不同类型的对象:
class processor {
public:
template <typename T, typename Handler>
void register_handler(Handler handler) {
handlers[type_id<T>()] = [handler](void const* v) {
handler(*static_cast<T const*>(v));
};
}
template <typename T>
void process(T const& t) {
auto it = handlers.find(type_id<T>());
if (it != handlers.end()) {
it->second(&t);
} else {
throw std::runtime_error("handler not registered");
}
}
private:
std::map<int*, std::function<void (void const*)>> handlers;
};
Run Code Online (Sandbox Code Playgroud)
这个类可能会这样使用:
processor p;
p.register_handler<int>([](int const& i) {
std::cout << "int: " << i << "\n";
});
p.register_handler<float>([](float const& f) {
std::cout << "float: " << f << "\n";
});
try {
p.process(42);
p.process(3.14f);
p.process(true);
} catch (std::runtime_error& ex) {
std::cout << "error: " << ex.what() << "\n";
}
Run Code Online (Sandbox Code Playgroud)
感谢大家的帮助.我接受了@StoryTeller的答案,因为他已经概述了为什么解决方案应该根据C++的规则有效.但是,@ SergeBallesta和评论中的其他一些人指出,MSVC执行的优化令人不安地接近于打破这种方法.如果需要更强大的方法,那么使用解决方案std::atomic可能更好,正如@galinette所建议的那样:
std::atomic_size_t type_id_counter = 0;
template <typename T>
std::size_t type_id() {
static std::size_t const x = type_id_counter++;
return x;
}
Run Code Online (Sandbox Code Playgroud)
如果有人有进一步的想法或信息,我仍然渴望听到它!
Sto*_*ica 27
是的,它在某种程度上是正确的.模板函数是隐式的inline,函数中的静态对象在inline所有翻译单元之间共享.
因此,在每个翻译单元中,您将获得调用的相同静态局部变量的地址type_id<Type>().此处受到标准的ODR违规保护.
因此,本地静态的地址可以用作一种自制的运行时类型标识符.
Ser*_*sta 11
这与标准一致,因为C++使用模板而不是像Java这样的类型擦除的泛型,因此每个声明的类型都有自己的包含静态变量的函数实现.所有这些变量都是不同的,因此应具有不同的地址.
问题在于它们的价值从未使用过,而且从未改变过.我记得优化器可以合并字符串常量.由于优化器尽力比任何人类程序员更聪明,我担心过于热心的优化编译器会发现,因为这些变量值永远不会改变,所以它们都将保持0值,所以为什么不将它们全部合并到节省内存?
我知道,由于as as规则,编译器可以自由地做它想要的,只要可观察的结果是相同的.而且我不确定始终共享相同值的静态变量的地址是否应该不同.也许有人可以确认标准的哪一部分真正关心它?
当前编译器仍然单独编译程序单元,因此无法确定其他程序单元是否将使用或更改该值.所以我的观点是优化器没有足够的信息来决定合并变量,你的模式是安全的.
但由于我真的不认为该标准可以保护它,我不能说未来版本的C++构建器(编译器+链接器)是否会发明一个全局优化阶段,主动搜索可以合并的未更改变量.或多或少与他们主动搜索UB以优化代码部分相同...只有普通模式,不允许它们会破坏过大的代码库受到保护,我不认为你的常见.
防止优化阶段合并具有相同值的变量的相当愚蠢的方法只是为每个变量赋予不同的值:
int unique_val() {
static int cur = 0; // normally useless but more readable
return cur++;
}
template <typename T>
void * type_id() {
static int x = unique_val();
return &x;
}
Run Code Online (Sandbox Code Playgroud)
好吧,这甚至没有尝试线程安全,但这不是问题:值永远不会被使用.但是你现在有不同的变量具有静态持续时间(按照@StoryTeller所述的每14.8.2标准),除了在竞争条件下有不同的值.由于它们使用得太多,它们必须具有不同的地址,您应该受到保护,以便将来改进优化编译器......
注意:我认为由于不会使用该值,因此返回void *声音更清晰......
只是从@bogdan的评论中偷走了一个额外的东西.众所周知,MSVC对/OPT:ICF标志进行了非常积极的优化.讨论表明它不应该符合,并且它只适用于标记为const的变量.但它强制执行我的观点,即使OP的代码看起来符合要求,如果没有生产代码中的额外预防措施,我也不敢使用它.
评论后编辑:我一开始没有意识到地址被用作键,而不是int值.这是一个聪明的方法,但它遭受了恕我直言的一个重大缺陷:目的是非常不清楚是否有其他人发现该代码.
它看起来像一个老C黑客.它聪明,高效,但代码并不能自我解释意图是什么.在现代c ++中,imho,这很糟糕.为程序员编写代码,而不是编译器.除非你已经证明存在严重的瓶颈,需要裸机优化.
我会说它应该有用,但我显然不是语言律师......
这里或这里可以找到一个优雅但复杂的constexpr解决方案
原始答案
它是"安全的",因为它是有效的c ++,你可以访问所有程序中返回的指针,因为静态local将在第一次函数调用时初始化.每个类型T都会有一个静态变量用在你的代码中.
但是:
此外,这种获取类型ID的方法只能在编译时工作,而不是在运行时使用多态对象.因此它永远不会从基本引用或指针返回派生类类型.
你将如何初始化静态int值?这里你没有初始化它们所以这是无效的.也许你想使用非const指针在某处初始化它们?
有两种更好的可能性:
1)专门为您想要支持的所有类型的模板
template <typename T>
int type_id() {
static const int id = typeInitCounter++;
return id;
}
template <>
int type_id<char>() {
static const int id = 0;
return id; //or : return 0
}
template <>
int type_id<unsigned int>() {
static const int id = 1;
return id; //or : return 1
}
//etc...
Run Code Online (Sandbox Code Playgroud)
2)使用全局计数器
std::atomic<int> typeInitCounter = 0;
template <typename T>
int type_id() {
static const int id = typeInitCounter++;
return id;
}
Run Code Online (Sandbox Code Playgroud)
最后一种方法是恕我直言,因为你不必管理类型.正如ASH所指出的,基于零的递增计数器允许使用a vector而不是a map更简单和有效.
另外,使用an unordered_map而不是a map,您不需要订购.这给你O(1)访问而不是O(log(n))
正如@StoryTeller所提到的,它在运行时运行良好.
这意味着你不能使用它,如下所示:
template<int *>
struct S {};
//...
S<type_id<char>()> s;
Run Code Online (Sandbox Code Playgroud)
而且,它不是固定的标识符.因此,您无法保证char通过可执行文件的不同运行绑定到相同的值.
如果你可以处理这些限制,那就没关系了.
如果您已经知道要使用持久标识符的类型,则可以使用类似的内容(在C++ 14中):
template<typename T>
struct wrapper {
using type = T;
constexpr wrapper(std::size_t N): N{N} {}
const std::size_t N;
};
template<typename... T>
struct identifier: wrapper<T>... {
template<std::size_t... I>
constexpr identifier(std::index_sequence<I...>): wrapper<T>{I}... {}
template<typename U>
constexpr std::size_t get() const { return wrapper<U>::N; }
};
template<typename... T>
constexpr identifier<T...> ID = identifier<T...>{std::make_index_sequence<sizeof...(T)>{}};
Run Code Online (Sandbox Code Playgroud)
并按照以下方式创建您的标识符:
constexpr auto id = ID<int, char>;
Run Code Online (Sandbox Code Playgroud)
您可以像使用其他解决方案一样使用这些标识符:
handlers[id.get<T>()] = ...
Run Code Online (Sandbox Code Playgroud)
此外,您可以在需要常量表达式的任何地方使用它们.
作为模板参数的示例:
template<std::size_t>
struct S {};
// ...
S<id.get<B>()> s{};
Run Code Online (Sandbox Code Playgroud)
在switch语句中:
switch(value) {
case id.get<char>():
// ....
break;
case id.get<int>():
// ...
break;
}
}
Run Code Online (Sandbox Code Playgroud)
等等.另请注意,只要不更改模板参数列表中类型的位置,它们就会通过不同的运行持久化ID.
主要缺点是在引入id变量时必须知道需要标识符的所有类型.
| 归档时间: |
|
| 查看次数: |
2384 次 |
| 最近记录: |