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 并不关心像 Volt 或 second 这样的单位,而只接受数字,所以我不想做任何检查你是否真的在 forceVoltage 中输入了 Volts。所以这也应该是可能的:

PowerMeter.forceVoltage(2 ms);

除了保持定义之外还有什么想法吗?

4

9 回答 9

18

不如通过为不同的电流创建类(ms,mV)来扭转它

例如

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

这对用户来说非常清楚,并且可以说不难阅读,而且您将免费获得类型检查。为不同的单元提供一个通用的基类将使其更容易实现。

于 2012-06-14T06:29:58.343 回答
9

您可以看到 Calum Grant 的库“ C++ Units ”作为如何实现它的一个很好的例子。该库有点过时,但仍然值得一看或可能会使用。

另外,我认为阅读以下内容可能会很有趣:“ SI UNITS 中的应用模板元编程:基于单元的计算库

还有一个很好的库:UDUNITS-2,它:

包含一个用于物理量单位的 C 库以及一个单位定义和值转换实用程序。

于 2012-06-14T06:32:35.350 回答
8

您可以对单位使用 C++11 的编译时有理算术支持,而不是为单位定义文字或宏。

于 2012-06-14T06:20:46.027 回答
6

看看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.
于 2012-06-14T14:05:25.393 回答
2

这就是我想出的……与 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;
}
于 2012-06-14T06:46:20.973 回答
2

我更喜欢尽可能避免使用宏,这是一个应该可行的例子。一种为您提供正确尺寸的轻量级解决方案是:

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

double distance = 10*m + 10*cm;

这也反映了单位是与价值相乘的物理概念。

于 2012-06-14T12:10:11.177 回答
1

考虑enum为您的单位使用 an 并将其作为第二个参数传递:

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在枚举周围可以避免单位名称污染全局命名空间。以这种方式使用枚举还强制在编译时将正确的物理单元传递给成员函数。

于 2012-06-14T06:34:43.287 回答
1

在你为任何更复杂的事情发疯之前,每当你编写将数量作为参数的新代码时,你应该像这样命名你的方法,以便它 100% 清晰:

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

同样使用具有正确名称的变量,例如:

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

这样,您是否必须转换并不重要,您在做什么仍然很清楚,例如

PowerMeter.settlingTimeInSeconds( minutes*60 );

当你准备好使用更强大的东西时,如果你真的需要,但要确保你不会失去使用哪个单元的清晰度。

于 2012-06-14T15:09:31.870 回答
1

我更喜欢 Anders K 的解决方案,但是您可以使用模板来节省一些时间,将所有单元实现为单独的类,这可能很耗时且容易出错,因为您可能需要手动编写大量代码:

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);
于 2012-06-14T08:05:59.090 回答