C ++ Правильное использование полиморфизма stati c - PullRequest
0 голосов
/ 29 марта 2020

Обзор

Короче говоря. Я реализовал самописный фреймворк для Stm32F7 и Stm32F4 для изучения C ++. В своем первом проекте я сильно полагался на виртуальные функции, но в недавней задаче у меня возникли проблемы с производительностью при использовании этого дизайна (задача, относящаяся к микросекундной синхронизации).

Поэтому я попытался использовать полиморфизм stati c, но я не уверен, что это хорошее решение. Я объясню свои проблемы в предоставленных примерах.

Обзор:
У меня есть интерфейс для функций GPIO. В этом сокращенном примере просто write Тогда есть различные спецификации MCU c реализации этого интерфейса. Я только что представил пример F7, но другие должны быть возможны. И, наконец, у меня есть домен, указанный c код. В этом примере некоторый датчик, который выбирает, использует GPIO для извлечения чего-то.

Внимание! Датчик не должен полагаться на указанное c оборудование. Следует полагаться на предоставленную абстракцию GPIO.

Решения, которые я нашел, кроме виртуальных функций

1: CRTP

// gpio interface
namespace V1 {
template<class D>
class Gpio
{
public:
    void write(bool status) {
        static_cast<D*>(this)->impl_write(status);
    };
};
}

// f7 specific gpio
namespace V1 {
class F7 : public V1::Gpio<F7> {
public:
    void impl_write(bool status) {

    }
};
}

// sensor
namespace V1 {
template <class GPIO_T>
class Sensor {
private:
    Gpio<GPIO_T> *gpio;
public:
    Sensor(Gpio<GPIO_T> *gpio) : gpio(gpio) {}
    void read() {
        return gpio->write(true);
    }
};
}

// main
using gpio_f7 = V1::F7;

V1::Gpio<gpio_f7> gpio {};
V1::Sensor<gpio_f7> sensor {&gpio};

Этот код фактически работает, а также устраняет накладные расходы на вызов из виртуальных функций во время компиляции , Но у него есть некоторые недостатки.

Поскольку я полагаюсь на параметры шаблона, все должно быть только заголовком.

Я не могу сохранить абстракцию Gpio *gpio, поскольку основание (V1::Gpio) нуждается в производном как шаблон, поэтому должно быть Gpio<GPIO_T> *gpio. Проблема в том, что это приводит к КАЖДОМУ КЛАССУ, который использует мои абстракции для храмования (как GPIO_T выше). Что для меня ощущается как огромный дефицит. Но так как я не в индустрии C ++, может, это вполне нормально?

2: Функции шаблонов

Другой вариант, который я нашел, это C -стиль шаблонных функций api.

// interface
namespace V2 {
template<class T>
void write(T& gpio, bool status) {
    gpio->write(status);
}
}

// gpio
namespace V2 {
class F7 {
public:
    void write(bool status) {

    }
};
}

// sensor
namespace V2 {
class Sensor {
private:
    F7 *gpio;
public:
    Sensor(F7 *gpio) : gpio(gpio) {

    }
    void read() {
        return write(gpio, true);
    }
};
}

// main
V2::F7 gpio {};
V2::Sensor sensor {&gpio};

IMO API для C -Style является для меня большим недостатком. Поскольку это скрывает функции, становится легче увидеть, какие функции доступны для объекта с помощью IDE. (Может быть, функции скрыты за разными включаемыми файлами)

Более того, я больше не могу сохранять абстракцию датчика и вынужден напрямую комментировать классы F7. Поэтому мне придется менять КАЖДЫЙ класс, если я хочу использовать другой MCU.

Так что я на самом деле не очень доволен обоими решениями. Я стараюсь переписать свои занятия в стиле CRTP. По крайней мере, для наиболее важных наиболее используемых классов, таких как GPIO, я предпочел бы иметь наибольшую производительность, которую я могу архивировать.

Итак, мои вопросы:

Я неправильно использую CRTP? Есть ли способ избавиться от храмования каждого класса, который использует абстракции? Или это нормально (идиоматизм c?), Чтобы храмовать каждый класс? Существуют ли другие решения (версия C ++ не имеет значения, может быть C ++ 20)?

1 Ответ

0 голосов
/ 29 марта 2020

В вашем первом решении, использующем CRTP, если вы уже предоставляете V1::F7 в качестве параметра шаблона, то какой смысл сохранять Gpio<GPIO_T> * в датчике? Вы могли бы просто сохранить GPIO_T m_impl и использовать это. Как вы сказали, проблема в том, что отпадает необходимость в том, чтобы реализация соответствовала интерфейсу, и что для каждой реализации Sensor<IMPL_TYPE> - это другой тип.

Итак, что вы хотите - это способ типа стирание это быстрее, чем с использованием виртуальных функций. Если вы уже знакомы с реализациями ранее, вы можете попробовать использовать std::variant:

#include <variant>

// gpio interface
namespace V1 {
class Gpio
{
public:
    virtual void write(bool) = 0;
};
}

// f7 specific gpio
namespace V1 {
class F7 : public V1::Gpio {
public:
    void write(bool status) override {

    }
};
}
// f8 specific gpio
namespace V1 {
class F8 : public V1::Gpio {
public:
    void write(bool status) override {

    }
};
}


// sensor
namespace V1 {
class Sensor {
private:
    std::variant<F7,F8> gpio;
public:
    template <typename T>
    Sensor(const T& t) : gpio(t) {}
    void read() {
        std::visit([](auto&& arg) { arg.write(true); },gpio);
    }
};
}

int main()
{
    V1::F8 impl;
    V1::Sensor s(impl);
    s.read();
    return 0;
}

Хотя согласно это вместо std::visit, вы можете попробовать std::get_if, так как это помечено constexpr

...