Вопрос по c++ – Как работать с модулями в интерфейсе c ++

21

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

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

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

#define mV *1.0e-03

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

int ms;

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

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

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

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

PowerMeter.forceVoltage(2 ms);

Любая идея помимо того, чтобы остаться с определениями?

Я мог бы сделать это, но это не естественный способ, которым пользователь хотел бы программировать. schnaufi
Почему ... Итак, вы хотите указать единицы измерения, но не хотите проверить, верны ли они? Это не имеет смысла. Кроме того, ваш "пользователь" знает C ++, но заботится о «естественном способе»? .. Почти немыслимо. Почему бы просто не включить его в имя функции? keltar
Нет, на самом деле большинство пользователей не являются программистами на С ++. Таким образом, мы хотим сохранить API простым. Включение в имя функции затруднительно, потому что чем бы мы имели forceVolt () forceMiliVolt () ... schnaufi
Можете ли вы передать свои единицы в качестве отдельной переменной?PowerMeter.forceVoltage(2, "ms"); Или, может быть, все выражение в виде строки? Blender
Обратите внимание, что все, что вы делаете, это присоединяете префиксы SI, а не единицы измерения (милли: = 1e-03not единица, это значение / префикс). Frank

Ваш Ответ

9   ответов
1

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

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

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

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

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

PowerMeter.settlingTimeInSeconds( minutes*60 );

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

9

C ++ Units& Quot; от Calum Grant как хороший пример того, как это реализовать. Библиотека немного устарела, но все же стоит посмотреть или ее стоит использовать.

Кроме того, я думаю, что было бы интересно прочитать: & quot;Прикладное шаблонное метапрограммирование в SI UNITS: библиотека вычислений на основе единиц& Quot;

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

contains a C library for units of physical quantities and a unit-definition and value-conversion utility.

+1 За пределами отношенийdimensions являются наиболее важными частями. С установленными размерами отношения (почти) приходят бесплатно.
6

Взгляни наBoost.Units. Here's some example code:

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

...

/// Test calculati,on 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.
Там такжеPhysUnits-CT-Cpp11небольшая библиотека C ++ 11, C ++ 14 только для заголовков для анализа измерений во время компиляции и манипулирования и преобразования единиц / количества. Проще, чем Boost.Units, зависит только от стандартной библиотеки C ++, только для SI, интегральных степеней измерений.
2

что я придумал ... почти такая же идея, как у Андерса К., но так как я написал код, я опубликую его:

#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;
}
8

рациональная арифметика во время компиляции поддержка юнитов вместо определения литералов или макросов для юнитов.

1

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

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);
1

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

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

double distance = 10*m + 10*cm;

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

Дополнительный комментарий: Вы можете использовать пространство имен для этого, и пользователь вашего API может затем решить, предпочитает ли он загромождать свое пространство имен или делать setVoltage (10 * CoolUnits :: m). Но все же лучше, чем определять вещи.
Мило с небольшими накладными расходами, но его недостаточно. Проблема в том, что $ 1m \ neq 1 $, но до $ m $. То есть $ 1 млн = 100 см $, но сами единицы также являются термином. Это проблема, когда человек пытается добавить температуру на расстоянии. Ваше решение позволит это.
18

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

например

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

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

Boost.Date_Time использует этот подход дляtime_duration типы (boost.org/doc/libs/1_49_0/doc/html/date_time/…).
1

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 пространство имен вокруг перечислений избегает загрязнения глобального пространства имен именами единиц. Использование перечислений таким образом также обеспечивает во время компиляции, что надлежащие физические единицы передаются функциям-членам.

Видя, как ОП пробовал литералы C ++ 11,enum class было бы более уместным.
Это правда. Я думаю, это зависит только от того, какой код пользователь хочет написать.
@chris: сenum classнет способа вытянуть все имена модулей в текущей области с помощьюusing директивы. Таким образом, вы должны поставить префикс перед каждой единицей (например,Voltage::volt).

Похожие вопросы