0

由于 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) {}
}

这里我们有一些类型ABC实现了一个接口IX并在数组中放置了指针ccf()然后我们为特定实例调用虚函数。(在像 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()

我的问题是:这种方法还有其他缺点/限制吗?

有什么改进吗?

4

0 回答 0