带有Scale的C ++维度分析(Barnes和Nackman)

时间:2015-02-24 14:36:51

标签: c++ design-patterns

我最近阅读了关于C ++ Source的系列文章,“暂停反映:五个五个列表”。在Part V中,Scott Meyers讨论了Barton和Nackman解决单位问题的方法。作为航空航天业的嵌入式软件工程师,这个特别的啊哈!片刻让我很兴奋。到目前为止,我还没有听说过这种方法(也不是这些作者)。

我已经做过研究,试图找到有关该解决方案的更多信息。我在这里看到了这个演示文稿:http://se.ethz.ch/~meyer/publications/OTHERS/scott_meyers/dimensions.pdf

我想我理解了我在这个解决方案上所阅读的所有内容。但我觉得有一块拼图丢失了。这个美丽,优雅的解决方案无处可扩展。具体来说,我对转换不仅仅是一个倍增因素感兴趣。例如,温度在开尔文,摄氏和华氏之间转换。我希望能够互换使用这些温度。

我的问题:

  1. 我错过了什么吗?是否参考我忽略的单位解决方案讨论讨论了规模?

  2. 如果没有,我该如何进一步解决这个问题?是否存在可与B& N方法结合使用以完成解决方案的现有模式?

  3. 我的目标是能够在不进行过多计算的情况下使用类似下面示例的代码。在距离的情况下,我希望能够声明一个定义为英里的对象,并以英里为单位执行所有相关计算,而不必经常来回转换为米。

    示例:

    typedef Units<double, miles>      uMiles;
    typedef Units<double, kilometers> uKilometers;
    
    uMiles      d1 (1.0);
    uKilometers d2 (1.60934);
    
    d1 += d2;
    if (d1.val(miles) == 2.0) // PASS
    if (d1.val(kilometers) == 3.21869) // PASS
    

    注意: 我已经看到了BOOST UNITS解决问题的方法,我不喜欢它。对我来说这是非常难以理解的。我也不是,通常允许使用外部库,如boost。

    备份数据:

    如上所述的单位类:

    template<class T, // Precision
        int m, // Mass
        int l, // Length
        int t, // Time
        int q, // Charge
        int k, // Temperature
        int i, // Luminous Intensity
        int a> // Angle
    
        class Units
        {
        public:
        // ------------------------------------------------------
        explicit
        Units (T initVal = 0)
            : val (initVal)
        {
        }
    
        // --------------------------------------------------------------------
        // Operator: Assignment from type T
        Units<T, m, l, t, q, k, i, a>&
        operator= (const T rhs)
        {
            val = rhs;
            return *this;
        }
    
        // --------------------------------------------------------------------
        // Operator: Type Converstion to T
        operator T () const
        {
            return val;
        }
    
        // --------------------------------------------------------------------
        // Operator: +=
        Units<T, m, l, t, q, k, i, a>&
        operator+= (const Units<T, m, l, t, q, k, i, a>& rhs)
        {
            val += rhs.val;
            return *this;
        }
    
        // --------------------------------------------------------------------
        Units<T, m, l, t, q, k, i, a>&
        operator-= (const Units<T, m, l, t, q, k, i, a>& rhs)
        {
            val -= rhs.val;
            return *this;
        }
    
        // --------------------------------------------------------------------
        Units<T, m, l, t, q, k, i, a>&
        operator*= (T rhs)
        {
            val *= rhs;
            return *this;
        }
    
        // --------------------------------------------------------------------
        Units<T, m, l, t, q, k, i, a>&
        operator/= (T rhs)
        {
            val /= rhs;
            return *this;
        }
    
        // --------------------------------------------------------------------
        // Get Reference
        T&
        Val ()
        {
            return val;
        }
    
        // --------------------------------------------------------------------
        // Get Value
        const T&
        Val () const
        {
            return val;
        }
    
        private:
        T val;
        };
    
    // ----------------------------------------------------------------------------
    // Operator: Addition
    template<class T, int m, int d, int t, int q, int k, int i, int a>
        const Units<T, m, d, t, q, k, i, a>
        operator+ (const Units<T, m, d, t, q, k, i, a> & lhs,
               const Units<T, m, d, t, q, k, i, a> & rhs)
        {
        Units<T, m, d, t, q, k, i, a> result (lhs);
        return result += rhs;
        }
    
    // ----------------------------------------------------------------------------
    // Operator: Subtraction
    template<class T, int m, int d, int t, int q, int k, int i, int a>
        const Units<T, m, d, t, q, k, i, a>
        operator- (const Units<T, m, d, t, q, k, i, a> & lhs,
               const Units<T, m, d, t, q, k, i, a> & rhs)
        {
        Units<T, m, d, t, q, k, i, a> result (lhs);
        return result -= rhs;
        }
    
    // ----------------------------------------------------------------------------
    // Operator: Multiplication
    template<class T, int m, int d, int t, int q, int k, int i, int a>
        const Units<T, m, d, t, q, k, i, a>
        operator* (const Units<T, m, d, t, q, k, i, a> & lhs,
               const Units<T, m, d, t, q, k, i, a> & rhs)
        {
        Units<T, m, d, t, q, k, i, a> result (lhs);
        return result *= rhs;
        }
    
    // ----------------------------------------------------------------------------
    // Operator: Division
    template<class T, int m, int d, int t, int q, int k, int i, int a>
        const Units<T, m, d, t, q, k, i, a>
        operator/ (const Units<T, m, d, t, q, k, i, a> & lhs,
               const Units<T, m, d, t, q, k, i, a> & rhs)
        {
        Units<T, m, d, t, q, k, i, a> result (lhs);
        return result /= rhs;
        }
    
    // ----------------------------------------------------------------------------
    // Operator: Multiplication (Creates New Type)
    template<class T,
            int m1, int d1, int t1, int q1, int k1, int i1, int a1,
        int m2, int d2, int t2, int q2, int k2, int i2, int a2>
    
        // Return Type
        Units<T, m1 + m2, d1 + d2, t1 + t2, q1 + q2, k1 + k2, i1 + i2, a1 + a2>
        operator* (const Units<T, m1, d1, t1, q1, k1, i1, a1>& lhs,
               const Units<T, m2, d2, t2, q2, k2, i2, a2>& rhs)
        {
            // New Return type
        typedef Units<T,
            m1 + m2,
            d1 + d2,
            t1 + t2,
            q1 + q2,
            k1 + k2,
            i1 + i2,
            a1 + a2> ResultType;
    
        return ResultType (lhs.Val() * rhs.Val());
        }
    
    // ----------------------------------------------------------------------------
    // Operator: Division (Creates New Type)
    template<class T,
            int m1, int d1, int t1, int q1, int k1, int i1, int a1,
        int m2, int d2, int t2, int q2, int k2, int i2, int a2>
    
        // Return Type
        Units<T, m1 - m2, d1 - d2, t1 - t2, q1 - q2, k1 - k2, i1 - i2, a1 - a2>
        operator/ (const Units<T, m1, d1, t1, q1, k1, i1, a1>& lhs,
               const Units<T, m2, d2, t2, q2, k2, i2, a2>& rhs)
        {
            // New Return type
        typedef Units<
            T,
            m1 - m2,
            d1 - d2,
            t1 - t2,
            q1 - q2,
            k1 - k2,
            i1 - i2,
            a1 - a2> ResultType;
    
        return ResultType (lhs.Val() / rhs.Val());
        }
    

    这个类允许我们编写如下代码:

    //  Base Types
    typedef Units<double, 1,0,0,0,0,0,0> uMass;
    typedef Units<double, 0,1,0,0,0,0,0> uLength;
    typedef Units<double, 0,0,1,0,0,0,0> uTime;
    typedef Units<double, 0,0,0,1,0,0,0> uCharge;
    typedef Units<double, 0,0,0,0,1,0,0> uTemperature;
    typedef Units<double, 0,0,0,0,0,1,0> uIntensity;
    typedef Units<double, 0,0,0,0,0,0,1> uAngle;
    
    //  Derived Types
    typedef Units<double, 0,2, 0,0,0,0,0> uArea;
    typedef Units<double, 0,3, 0,0,0,0,0> uVolume;
    typedef Units<double, 0,1,-1,0,0,0,0> uVelocity;
    typedef Units<double, 0,1,-2,0,0,0,0> uAcceleration;
    typedef Units<double, 1,1,-2,0,0,0,0> uForce;
    
    
    uMass         mass;
    uTime         time;
    uForce        force;
    uLength       length;
    uVelocity     velocity;
    uAcceleration acceleration;
    
    // This will compile
    mass = 7.2;
    acceleration = 3.5;
    force = mass * acceleration;
    
    // These will not compile ** Enforcing Dimensional Unit Correctness
    force = 7.2 * acceleration;
    force = mass; 
    force *= acceleration;
    

2 个答案:

答案 0 :(得分:0)

根据我对您的代码和解释的理解,您似乎可以定义转换常量“Unit s”,例如

Units<double,0,0,0,0,0,0,0> K2C(243.15);
Units<double,0,0,0,0,1,0,0> uCelsius;
Units<double,0,0,0,0,1,0,0> uKelvin;

uCelsius = uKelvin - K2C;

上面的代码与重载运算符一起使用,同时保持模板化单元的一致性。您必须为要使用的任何常量创建伪 - Unit

我能看到工作的另一种方式是写一个像

这样的函数
typdef enum {
    CELSIUS,
    KELVIN,
    FAHRENHEIT
} temp_t;

void Units::convertTemp(const temp_t from, const temp_t to) {

    switch(from) {
        case KELVIN:
            val -= 243.15;
        case CELSIUS:
            if(to == FAHRENHEIT)
                //conversion
            else if(to == KELVIN)
                val += 243.15;
            break;
        case FAHRENHEIT:
            // convert to celsius
            if(to == KELVIN)
                //convert to Kelvin
     }
}

答案 1 :(得分:0)

在物理学中,具有单位(如速度)的值乘以标量时保留其单位。这意味着说:

1.6 * 7 kilometers per hour = 11.2 kilometers per hour

不会更改单位。我们实际想要做的是将每小时公里数转换为每小时英里数,但我们只是将每小时公里数乘以一个因子。仅仅因为你得到的数字相当于每小时英里数并不意味着你实际上代表的是每小时里程数。

在模板定义中,您只允许每种基本单元

到目前为止,您已区分不同类型的物理量,但不区分不同类型的单位系统。在上面的代码中,您的单位/类型为Speed,而不是milesPerHour,您仍然可以手动记住您正在使用的实际单位,并手动转换它与SI单位(不使用类型系统)。实际上,如果你有一个变量初始化为每小时英里数而另一个变量为每小时公里数,那么它们是相同类型还是不同类型?我认为它们应该是不同的类型。

要使所有单位(在不同的测量系统中)不同类型,我们可以为SI units system编写一个Units模板,为{编写一个等效模板{3}}

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    class SIUnits
    {
    // ...

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    class ImperialUnits
    {
    // ...

现在我们需要弄清楚一种允许我们的类型系统在两个单位系统之间自动映射的好方法。

以下是一个可能的(但可怕)解决方案:

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    ImperialUnits<T, m, l, t, q, k, i, a>
    convert(SIUnits<T, m, l, t, q, k, i, a> value)
    {
        T conversionFactor = 1.0;
        for (int x = 0; x < m; ++x)
        {
            // This is some function that maps from one to the other.
            conversionFactor *= siMassToImperialMassFactor;
            conversionFactor += siMassToImperialMassOffset;
        }

        for (int x = m; x < 0; ++x)
        {
            // This is some function that maps from one to the other.
            conversionFactor *= siMassToImperialMassFactor;
            conversionFactor += siMassToImperialMassOffset;
        }

        // Do the same for other dimensions as well...

你看到Kelvin,Fahrenheit和Celsius的问题在于你没有改变测量的基础系统这一事实,这需要你制作一个新的类型,但只是手动记住小提琴假装在两个系统之间进行转换所需的因素。

从本质上讲,如果我们删除模板并使用纯类,我们会有类似的东西:

class Celsius;
class Kelvin;
class Fahrenheit
{
    // ...
    Fahrenheit(Celsius t); // Auto-convert from celsius
    Fahrenheit(Kelvin t);  // Auto-convert from Kelvin
    // ...

这些是不同的类型,因此我们可以使用类型系统来定义它们之间的转换。

最后,我可以解释一下我实际提出的建议。在Units模板中,您拥有每个物理维度的基础类型和标识符,其中一个解决方案是添加一个项目来表示所使用的测量系统,即

template<class T, // Precision
    int SystemOfUnits, // Denotes the system of units used, SI or Imperial.
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    class Units
    {
        // etc.

但是,我不喜欢上述解决方案,因为SystemOfUnits模板参数的值会编码有关模板的过多信息。在我的定义中,我可能会更明确地说明我的测量系统使用的单位,即

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a, // Angle
    class M, // Mass unit type
    class L, // Length unit type
    class T, // Time unit type
    class Q, // Charge unit type
    class K, // Temperature unit type
    class I, // Luminous Intensity unit type
    class A> // Angle unit type


    class Units
    {
        // etc.

这将迫使您使用一致的测量系统进行尺寸分析,但代价是模板参数加倍。