由于avr-g ++将vtable放在RAM中,我使用静态多态性写了一个替代品,这是事实。
考虑以下示例:
volatile uint8_t x;
struct IX {
virtual void f() const = 0;
// virtual ~IX() = default; // need delete
};
struct A : public IX {
const uint8_t v = 0;
void f() const override {
x = v;
}
};
struct B : public IX {
const uint8_t v = 1;
void f() const override {
x = v;
}
};
struct C : public IX {
const uint8_t v = 2;
void f() const override {
x = v;
}
};
volatile uint8_t index = 2;
int main() {
A a;
B b;
C c;
const std::array<const IX*, 3> cc{&a, &b, &c};
cc[index]->f();
while(true) {}
}
此处我们有一些类型A
,B
和C
实现接口IX
并在数组cc
中放置指针。然后我们为特定实例调用虚函数f()
。 (在像AVR这样的小型μC上使用它,存在RAM的“浪费”,因为vtable被放置在RAM中并且每个对象包含vptr,并且由于f()
的间接调用而导致性能损失。
所以我在这种情况下寻找替代解决方案:最简单的方法是使用异构容器,如std::tuple
并编写一个switch语句:
const std::tuple<A, B, C> t;
auto f = [](const auto& v) {
v.f();
};
switch (index) {
case 0:
f(std::get<0>(t));
break;
case 1:
f(std::get<1>(t));
break;
case 2:
f(std::get<2>(t));
break;
default:
assert(false);
break;
}
这产生了optimale机器代码,但它是一种不灵活的解决方案。所以我写了一个元函数来调用f()
来获取元组的特定元素:
const std::tuple<A, B, C> t;
Meta::visitAt(t, index, [](const auto& v){v.f();});
实现如下:
namespace Meta {
namespace detail {
template<uint8_t N>
struct visit {
template<typename T, typename F>
static void at(T& tuple, uint8_t index, const F& f) {
if (index == (N - 1)) {
f(std::get<N - 1>(tuple));
}
else {
visit<N - 1>::at(tuple, index, f);
}
}
};
template<>
struct visit<0> {
template<typename T, typename F>
static void at(T&, uint8_t , const F&) {
assert(false);
}
};
template<typename T, typename F, size_t... I>
void all(const T& tuple, const F& f, std::index_sequence<I...>) {
(f(std::get<I>(tuple)), ...);
}
}
template<typename... T, typename F>
void visitAt(const std::tuple<T...>& tuple, uint8_t index, const F& f) {
detail::visit<sizeof...(T)>::at(tuple, index, f);
}
template<typename... T, typename F>
void visitAt(std::tuple<T...>& tuple, uint8_t index, const F& f) {
detail::visit<sizeof...(T)>::at(tuple, index, f);
}
template<typename... T, typename F>
void visit(const std::tuple<T...>& tuple, const F& f) {
detail::all(tuple, f, std::make_index_sequence<sizeof...(T)>{});
}
}
这在我的场景中非常有效,但显然仅限于静态容器(如std::tuple
)。还有一个for-each-like迭代Meta::visit()
。
我的问题是:这种方法还有其他缺点/限制吗?
有任何改进吗?