我想简化我在我的应用程序中编写的代码,该代码处理多种数据结构类型但具有公共头。鉴于这样的事情:
enum class MyType {
Foo = 100,
Bar = 200,
};
struct Hdr {
MyType type;
};
struct Foo {
Hdr hdr;
int x;
int y;
int z;
};
struct Bar {
Hdr hdr;
double value;
double ratio;
};
void process(const Foo *ptr)
{
// process Foo here
}
void process(const Bar *ptr)
{
// process Bar here
}
extern void *getData();
int main()
{
const void *pv = getData();
auto pHdr = static_cast<const Hdr *>(pv);
switch (pHdr->type) {
case MyType::Foo: process(static_cast<const Foo *>(pv)); break;
case MyType::Bar: process(static_cast<const Bar *>(pv)); break;
default: throw "Unknown";
}
return 0;
}
理想情况下,我想用以下内容替换上面的switch语句:
process(multi_cast<pHdr->type>(pv);
我完全可以写这样的语句来让它起作用:
template<MyType::Foo>
const Foo *multi_cast(void *p)
{
return static_cast<const Foo *>(p);
}
template<MyType::Bar>
const Bar *multi_cast(void *p)
{
return static_cast<const Bar *>(p);
}
但是我不能写模板参数是枚举的模板(或者就此而言是int) 我只是看了这么久才能看到答案吗? 或者没有其他方法可以做到这一点?
答案 0 :(得分:3)
没有其他方法可以做到。
正如评论所指出的那样,由于类型在运行时存储在头文件中,因此您必须进行某种类型的运行时查找;没有任何模板或重载解析可以帮助您,因为所有这些都是在编译时。
您可以根据需要抽象查找,但是您只能将switch语句替换为其他类型的查找,并且只有在远离简单的切换/查找表时才能降低性能。
例如,你可以从这样的事情开始并坚持下去:
#include <iostream>
#include <cassert>
enum class Type {
FOO,
BAR,
NUM_
};
struct Header {
Header(Type t)
: type(t)
{}
Type type;
};
struct Foo {
Foo(int x, int y, int z)
: header(Type::FOO), x(x), y(y), z(z)
{}
Header header;
int x;
int y;
int z;
};
struct Bar {
Bar(double value, double ratio)
: header(Type::BAR), value(value), ratio(ratio)
{}
Header header;
double value;
double ratio;
};
static inline void process(Foo*) {
printf("processing foo...\n");
}
static inline void process(Bar*) {
printf("processing bar...\n");
}
using ProcessFunc = void(*)(void*);
static ProcessFunc typeProcessors[(size_t)Type::NUM_] = {
[](void* p) { process((Foo*)p); },
[](void* p) { process((Bar*)p); },
};
static void process(void* p) {
Type t = ((Header*)p)->type;
assert((size_t)t < (size_t)Type::NUM_ && "Invalid Type.");
typeProcessors[(size_t)t](p);
}
static void* get_foo()
{
static Foo foo(0, 0, 0);
return &foo;
}
static void* get_bar()
{
static Bar bar(0.0, 0.0);
return &bar;
}
int main() {
Foo foo(0, 0, 0);
Bar bar(0.0, 0.0);
process(&foo);
process(&bar);
process(get_foo());
process(get_bar());
return 0;
}
然而你只是变得可爱而且很可能变慢。您也可以将开关放在process(void*)
如果您没有对数据进行序列化(可疑),主要是一次处理一种类型,并且想要一个OO解决方案(我不会),您可以返回类型继承的基类型来自并添加一个纯虚拟process
函数,如下所示:
struct Type {
virtual void process() = 0;
virtual ~Type() {}
};
struct Foo : Type {
int x = 0;
int y = 0;
int z = 0;
virtual void process() override {
printf("processing foo...\n");
}
};
struct Bar : Type {
double value = 0.0;
double ratio = 0.0;
virtual void process() override {
printf("processing bar...\n");
}
};
static Type* get_foo() {
static Foo foo;
return &foo;
}
static Type* get_bar() {
static Bar bar;
return &bar;
}
int main() {
Foo foo;
Bar bar;
foo.process();
bar.process();
get_foo()->process();
get_bar()->process();
return 0;
}
我会坚持使用开关,但是我会将Type :: FOO和Type :: BAR的值保持为默认的0和1.如果你把这些值搞得太多,编译器可能决定将开关实现为一堆分支而不是查找表。
答案 1 :(得分:-1)
您正在使用可称为静态(=编译时)多态的内容。这需要生成此类switch
语句,以便将运行时值pHrd->dtype
转换为case
子句中的一个编译时值句柄。像你的
process(multi_cast<pHdr->type>(pv);
是不可能的,因为在编译时不知道pHdr->type
。
如果你想避开switch
,你可以使用普通的动态多态和忘记关于enum Hdr
,但是使用抽象基础类
struct Base {
virtual void process()=0;
virtual ~Base() {}
};
struct Foo : Base { /* ... */ };
struct Bar : Base { /* ... */ };
Base*ptr = getData();
ptr->process();
答案 2 :(得分:-1)
您有两个问题:
第2点是继承与virtual
成员函数一起用于:
struct Thing {
virtual void doStuff() const = 0;
virtual ~Thing() {}
};
struct AThing : Thing {
void doStuff() const override { std::cout << "A"; }
};
struct BThing : Thing {
void doStuff() const override { std::cout << "B"; }
};
通常通过创建某种“工厂”机制来解决第1点,然后根据运行时值将调度分配给其中一个工厂。首先,工厂:
Thing * factoryA() { return new AThing(); }
Thing * factoryB() { return new BThing(); }
Thing * factory_failure() { throw 42; }
“调度”(或“选择正确的工厂”)可以用不同的方式完成,其中一种是你的switch
声明(快速,但笨拙),通过某个容器/阵列进行线性搜索(简单,慢)或通过在地图中查找(对数 - 或基于散列的地图的常量)。
我选择了(有序)地图,但我没有使用std::map
(或std::unordered_map
),而是使用(排序!)std::array
来避免动态内存分配:
// Our "map" is nothing more but an array of key value pairs.
template <
typename Key,
typename Value,
std::size_t Size>
using cmap = std::array<std::pair<Key,Value>, Size>;
// Long type names make code hard to read.
template <
typename First,
typename... Rest>
using cmap_from =
cmap<typename First::first_type,
typename First::second_type,
sizeof...(Rest) + 1u>;
// Helper function to avoid us having to specify the size
template <
typename First,
typename... Rest>
cmap_from<First, Rest...> make_cmap(First && first,
Rest && ... rest) {
return {std::forward<First>(first), std::forward<Rest>(rest)...};
}
使用std::lower_bound
我在这个有序数组(ehm“map”)上执行二进制搜索:
// Binary search for lower bound, check for equality
template <
typename Key,
typename Value,
std::size_t Size>
Value get_from(cmap<Key,Value,Size> const & map,
Key const & key,
Value alternative) {
assert(std::is_sorted(std::begin(map), std::end(map),
[](auto const & lhs, auto const & rhs) {
return lhs.first < rhs.first; }));
auto const lower = std::lower_bound(std::begin(map), std::end(map),
key,
[](auto const & pair, auto k) {
return pair.first < k; });
if (lower->first == key) {
return lower->second;
} else {
// could also throw or whatever other failure mode
return alternative;
}
}
所以,最后,我可以使用static const
映射来获得一个工厂给定一些运行时值“type”(或选择,因为我命名):
int main() {
int const choices[] = {1, 10, 100};
static auto const map =
make_cmap(std::make_pair(1, factoryA),
std::make_pair(10, factoryB));
try {
for (int choice : choices) {
std::cout << "Processing choice " << choice << ": ";
auto const factory = get_from(map, choice, factory_failure);
Thing * thing = factory();
thing->doStuff();
std::cout << std::endl;
delete thing;
}
} catch (int const & value) {
std::cout << "Caught a " << value
<< " ... wow this is evil!" << std::endl;
}
}
该“地图”的初始化可能是constexpr
。
当然,您应该使用托管指针(如Thing *
)而不是原始指针(std::unique_ptr
)。此外,如果您不希望将处理(doStuff
)作为成员函数,那么只需调用一个给定函数对象的单个“调度”(virtual
)成员函数,自己的实例(this
)。使用CRTP基类,您不需要为每个类型实现该成员函数。