Генерация во время компиляции подмножества методов класса - PullRequest
2 голосов
/ 04 января 2012

Я разрабатываю программный интерфейс для нескольких устройств разных производителей.Большинство производителей обычно выпускают не менее десятка моделей.Команды и данные отправляются на устройства в виде инструкций низкого уровня.Проблема заключается в том, что хотя нет двух устройств, поддерживающих один и тот же набор инструкций, существует значительное совпадение между набором инструкций, поддерживаемым большинством устройств.

Поскольку инструкции низкого уровня изворотливы, я планирую обернуть их в интуитивно-названные методы классов, чтобы мне не приходилось искать документы при написании или чтении (или отладке) кода.В первой версии моего проекта все методы будут принадлежать классу Device, конструктор которого принимает единственный параметр, представляющий собой перечисление, указывающее модель устройства.Например:

  class Device
  {
  public:
       enum Model{ ABC , KLM , XYZ };
       Device( Model _model );        // ctor

       // Commands (encapsulate low-level instructions)
       inline void do_Foo();           // supported by all models
       inline void do_Bar();           // unsupported by 'KLM'
  };

Однако, кроме того, я бы хотел запретить вызов командных методов, если они не поддерживаются моделью, с которой инициализирован Device.Фактически, я хотел бы генерировать ошибку времени компиляции, если, например, do_Bar() вызывается для модели устройства KLM.Я исключил создание класса для каждой модели устройства, потому что это потребовало бы создания множества классов.

Мысли

Я рассмотрел вопрос об использовании директивы препроцессора #error для генерации ошибок во время компиляции с использованием текущей модели устройства в качестве предиката или предварительного условия, хотя я не уверен, поддерживают ли макросы препроцессора #if.. неконстанты, такие как модели моего устройства.В идеальном мире командный метод будет помечен с помощью методов, которые поддерживают и, следовательно, могут вызывать его.Тем не менее, и я надеюсь, что я не буду просить слишком много, я бы хотел, чтобы это было сделано как можно проще, чтобы добавление поддержки для новых устройств было относительно простым и не включало слишком много (подверженных ошибкам) ​​правок.

Задумка: я понимаю, что замысел может быть ошибочным, потому что все методы должны вызываться.Я предполагаю, что подмножество допустимых команд все еще может быть сгенерировано для каждого устройства, использующего STL, хотя я не знаю, какая парадигма STL (например, черты) применяется в этом случае.

Ответы [ 3 ]

3 голосов
/ 04 января 2012

Вы не можете принимать решения о времени компиляции для чего-то, что известно только во время выполнения (например, параметр, передаваемый компилятору). Поэтому у вас есть два варианта:

1) генерировать исключение во время выполнения, когда вызывается неподдерживаемый метод

inline void do_Bar(){
  if(model == KLM) throw runtime_exception("do_bar unsupported by device");
  ...
}

2) Создайте много классов, возможно, с помощью шаблонов, которые содержат только соответствующие методы. Один из способов сделать это заключается в следующем:

  enum Model{ ABC , KLM , XYZ };
  template<Model M>
  class Device {
  public:       
       Device();        // ctor
       // Commands (encapsulate low-level instructions)
       inline void do_Foo();           // supported by all models
       template<Bool Dummy = true>
       inline typename std::enable_if<Dummy && (M != KLM), void>::type do_Bar(); // unsupported by 'KLM'
  };

Требуется параметр шаблона Dummy, поскольку enable_if зависит от SFINAE, который будет работать только в том случае, если сам метод является методом шаблона, а enable_if зависит от параметра шаблона. Поскольку это параметр шаблона по умолчанию, его не нужно явно указывать при вызове метода, поэтому

Device<ABC> d;
d.do_bar();

все равно будет работать (поэтому никаких изменений в интерфейсе нет).

Я использовал std::enable_if, который доступен только на C ++ 11, если у вас его нет, вам нужно либо использовать boost::enable_if, либо написать его самостоятельно (это не то, что hard ).

Второй вариант имеет тот недостаток, что невозможно написать код, который не знает базовой модели. С положительной стороны это позволяет маскировать небольшие различия в предлагаемых интерфейсах посредством частичной специализации (или использования enable_if) для получения различных реализаций для разных моделей.

boost::enable_if отличается от std::enable_if тем, что в качестве первого параметра он принимает тип вместо логического. Таким образом, можно использовать boost::enable_if_c, который работает так же, как std::enable_if, или использовать boost::enable_if в сочетании с boost::integral_constant (который является частью черт типа Boost, поэтому включите boost/type_traits.hpp):

template<Bool B> typename boost::enable_if<boost::integral_constant<bool, B && (M != KLM)>, void>::type do_bar();
3 голосов
/ 04 января 2012

То, что вам нужно, это, по сути, статический полиморфизм : изменяющиеся свойства класса во время компиляции в зависимости от его типа. Чтобы сделать это, вам нужно заменить проверку модели во время выполнения с помощью этого перечисления проверкой во время компиляции с использованием типов. Просто создайте группу классов, по одному для каждой вашей модели, и используйте наследование для совместного использования общего кода. CRTP пригодится.

template<class Model>
class Device {
protected:
    void do_foo();
    void do_bar();
};

class ModelABC : public Device<ModelABC> {

};

class ModelKLM : public Device<ModelKLM> {
private:
    void do_bar(); // not available for this model, private!
};

class ModelXYZ : public Device<ModelXYZ> {

};

//-------- common implementation for all models

template<class Model>
void Device<Model>::do_foo() {
    std::cout << "Device::do_foo()\n";
}

template<class Model>
void Device<Model>::do_bar() {
    std::cout << "Device::do_bar()\n";
}

//-------- special implementation of method do_foo() for model XYZ
template<>
void Device<ModelXYZ>::do_foo() {
    std::cout << "special implementation of do_foo() for model XYZ\n";
}

void test() {
    ModelABC abc;
    ModelKLM klm;
    ModelXYZ xyz;

    abc.do_foo();
    klm.do_foo();
    xyz.do_foo();

    abc.do_bar();
    //klm.do_bar(); // compile-time error!
    xyz.do_bar();
}

Обратите внимание, что вы можете реализовать любое специфичное для модели поведение, специализируя шаблоны соответствующих методов. Также вы можете сделать некоторые методы модели недоступными во время компиляции с помощью модификатора private.


EDIT

В несколько более декларативной форме вы можете использовать частное наследование, чтобы выразить, какие методы доступны в каждой модели, а не какие нет. Рассмотрим код:

template<class Model>
class Device {
protected:
    void do_foo();
    void do_bar();
};

class ModelABC : private Device<ModelABC> {
public:
    using Device<ModelABC>::do_foo;
    using Device<ModelABC>::do_bar;
};

class ModelKLM : private Device<ModelKLM> {
public:
    using Device<ModelKLM>::do_foo;
};

class ModelXYZ : private Device<ModelXYZ> {
public:
    using Device<ModelXYZ>::do_foo;
    using Device<ModelXYZ>::do_bar;
};

Этот фрагмент аналогичен предыдущему: модель KLM не имеет метода do_bar(), а модель XYZ имеет специализированный метод do_foo().

0 голосов
/ 04 января 2012

Рассмотрим класс вашего устройства выше.Когда вы компилируете класс, компилятор создает блок данных с достаточным пространством для выделения для членов данных, а для функций он создает функции с именами искаженные и к ним добавляется новый параметр, которыйуказатель «это».

Ваш конструктор тоже просто еще одна функция для компилятора, хотя семантический анализатор позаботится о том, чтобы у него не было возвращаемого типа, а была просто другой функцией, то есть списком инструкций по некоторому адресу памяти, готовому к выполнению.,Когда функции генерируются в машинном коде, все, что делает компилятор, - это выдает инструкции по настройке стека для хранения параметров, выплевывает фактический код функции и очищает стек.Он не имеет представления о значении, которое может быть сохранено в параметрах, и, следовательно, его нельзя заставить выдать ошибку!

Тем не менее, вы можете рассмотреть возможность использования статических анализаторов, таких как clang , гдеВы можете добавить свои правила для анализа своего кода и выдать ошибку при соответствующем нарушении правила.Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...