由于 avr-g++ 将 vtables 放在 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;
}
这可以优化机器代码,但它是一个不灵活的解决方案。所以我写了一个元函数来调用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()
。
我的问题是:这种方法还有其他缺点/限制吗?
有什么改进吗?