Как обрабатывать блоки в интерфейсе С++

В настоящее время я разрабатываю API, где хочу, чтобы пользователь мог писать такой код:

PowerMeter.forceVoltage(1 mV);
PowerMeter.settlingTime(1 ms);

В настоящее время мы делаем это с помощью таких как:

#define mV *1.0e-03

Это очень удобно для пользователя, чтобы написать свой код, и он также очень читабельен, но, конечно же, имеет и недостатки:

int ms;

Выбросит некоторые ошибки компилятора, которые трудно понять. Поэтому я ищу лучшее решение.

Я попробовал новые С++ 11 литералы, но с этим я мог бы достичь:

long double operator "" _mV(long double value) {
  return value * 1e-3;
}
PowerMeter.forceVoltage(1_mV);

В конце API не заботится об устройстве наподобие Volt или second, но принимает только номер, поэтому я не хочу делать никаких проверок, если вы действительно вводите Volts в forceVoltage или нет. Это также должно быть возможно:

PowerMeter.forceVoltage(2 ms);

Любая идея, кроме останова с определениями?

Ответ 1

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

static double m = 1;
static double cm = 0.1;
static double mV = 0.001;

double distance = 10*m + 10*cm;

Это также отражает физическую концепцию того, что единицы - это нечто, умноженное на значение.

Ответ 2

как бы вместо этого немного повернуть его, создав классы (ms, mV) для разных токов

например.

PowerMeter.forceVoltage( mV(1) );  
PowerMeter.settlingTime( ms(1) )

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

Ответ 3

Вы можете увидеть библиотеку " С++ Units" из Calum Grant как хороший пример того, как это реализовать. Библиотека немного устарела, но все равно стоит ее увидеть или использовать.

Кроме того, я думаю, было бы интересно прочитать: " Прикладной шаблон метапрограммирования в SI UNITS: Библиотека модульных вычислений

Есть еще одна хорошая библиотека: UDUNITS-2, которая:

содержит C-библиотеку для единиц физических величин и утилиту определения единиц измерения и преобразования значений.

Ответ 5

Взгляните на Boost.Units. Вот пример кода:

quantity<energy>
work(const quantity<force>& F, const quantity<length>& dx)
{
    return F * dx; // Defines the relation: work = force * distance.
}

...

/// Test calculation of work.
quantity<force>     F(2.0 * newton); // Define a quantity of force.
quantity<length>    dx(2.0 * meter); // and a distance,
quantity<energy>    E(work(F,dx));  // and calculate the work done.

Ответ 6

Вот что я придумал... в значительной степени ту же идею, что и Anders K, но так как я написал код, я отправлю его:

#include <iostream>

using namespace std;

class MilliVoltsValue;
class VoltsValue;

class VoltsValue
{
public:
   explicit VoltsValue(float v = 0.0f) : _volts(v) {/* empty */}
   VoltsValue(const MilliVoltsValue & mV);

   operator float() const {return _volts;}

private:
   float _volts;
};

class MilliVoltsValue
{
public:
   explicit MilliVoltsValue(float mV = 0.0f) : _milliVolts(mV) {/* empty */}
   MilliVoltsValue(const VoltsValue & v) : _milliVolts(v*1000.0f) {/* empty */}

   operator float() const {return _milliVolts;}

private:
   float _milliVolts;
};

VoltsValue :: VoltsValue(const MilliVoltsValue & mV) : _volts(mV/1000.0f) {/* empty */}

class PowerMeter
{
public:
   PowerMeter() {/* empty */}

   void forceVoltage(const VoltsValue & v) {_voltsValue = v;}
   VoltsValue getVoltage() const {return _voltsValue;}

private:
   VoltsValue _voltsValue;
};

int main(int argc, char ** argv)
{
   PowerMeter meter;

   meter.forceVoltage(VoltsValue(5.0f));
   cout << "Current PowerMeter voltage is " << meter.getVoltage() << " volts!" << endl;

   meter.forceVoltage(MilliVoltsValue(2500.0f));
   cout << "Now PowerMeter voltage is " << meter.getVoltage() << " volts!" << endl;

   // The line below will give a compile error, because units aren't specified
   meter.forceVoltage(3.0f);   // error!

   return 0;
}

Ответ 7

Рассмотрим использование enum для ваших устройств и передайте его как второй параметр:

namespace Units
{
    enum Voltage
    {
        millivolts = -3,
        volts = 0,
        kilovolts = 3
    };

    enum Time
    {
        microseconds = -6,
        milliseconds = -3,
        seconds = 0
    };
}

class PowerMeter
{
public:
    void forceVoltage(float baseValue, Units::Voltage unit)
    {
         float value = baseValue * std::pow(10, unit);
         std::cout << "Voltage forced to " << value << " Volts\n";
    }

    void settlingTime(float baseValue, Units::Time unit)
    {
         float value = baseValue * std::pow(10, unit);
         std::cout << "Settling time set to " << value << " seconds\n";
    }
}

int main()
{
    using namespace Units;
    PowerMeter meter;
    meter.settlingTime(1.2, seconds);
    meter.forceVoltage(666, kilovolts);
    meter.forceVoltage(3.4, milliseconds); // Compiler Error
}

Обтекание пространства имен Units вокруг перечислений позволяет избежать загрязнения глобального пространства имен именами модулей. Использование перечислений таким образом также обеспечивает во время компиляции, что правильная физическая единица передается функциям-членам.

Ответ 8

Я предпочитаю решение от Anders K, однако вы можете использовать шаблон, чтобы сэкономить некоторое время на реализацию всех единиц как класс separte, который может быть временным и подвержен ошибкам, поскольку вам может понадобиться написать много кода вручную:

enum Unit {
    MILI_VOLT = -3,
    VOLT = 0,
    KILO_VOLT = 3
};

class PowerMeter
{
public:

    template<int N>
    void ForceVoltage(double val)
    {
        std::cout << val * pow(10.0, N) << endl;
    };
};

Используйте это:

        PowerMeter pm;
        pm.ForceVoltage<MILI_VOLT>(1);
        pm.ForceVoltage<VOLT>(1);
        pm.ForceVoltage<KILO_VOLT>(1);

Ответ 9

Прежде чем сходить с ума на что-то более сложное, всякий раз, когда вы пишете новый код, который принимает количество в качестве аргумента, вы должны называть свои методы таким образом, чтобы он на 100% очистил:

PowerMeter.forceInMilliVolts( ... )
PowerMeter.settlingTimeInSeconds( ... )

И аналогичным образом используйте переменные с правильными именами, например:

int seconds(10);
int milliVolts(100);

Таким образом, не имеет значения, нужно ли преобразовать, все еще ясно, что вы делаете, например.

PowerMeter.settlingTimeInSeconds( minutes*60 );

Когда вы будете готовы к чему-то более сильному движению к этому, если вам действительно нужно, но убедитесь, что вы не потеряете ясность того, какое устройство используется.