65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 中的测量单位类型。使用编译时模板编程。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (26投票s)

2014 年 9 月 12 日

CPOL

32分钟阅读

viewsIcon

81758

downloadIcon

626

一个轻量级的库,支持多种单位类型,它们之间无缝隐式缩放,并且能够高效地处理多个无因子基单位系统(例如 MKS 和 CGS)。

这是该库的第二个版本 - 更改的详细信息可以在末尾的“历史记录”部分找到。有一些修复,提供了一种方便的方式来定义单位作为另一个单位的倒数,并且现在正确处理基于基准的测量,例如摄氏度或高于基准的高度。

目录

#引言, #背景, #特性, #基准测量#快速参考,
#量纲分析的封装, #单位类型的架构
#第二个版本中的代码更改#最后评论#历史记录

简介

本文档可能对任何正在寻找一个独立且可用的测量单位库的人感兴趣,同时也是一个说明,展示了如何使用模板来扩展编译器的工作,在不生成任何额外运行时代码的情况下,检查和解释您编写的代码。

It 不是不透明的模板元编程,但它轻微使用了模板元编程的构建技术。它很轻量级,因为它不生成任何额外的运行时代码,而且也不会给编译器或试图理解其工作原理的程序员带来负担。我开发这项技术时并没有掌握所有模板技巧,而是通过解决具体问题来逐步掌握的。我相信这种“工程”方法可能非常适合许多程序员的生活和思维方式。

它不需要 C++ 较新版本提供的任何功能。constexpr 在此上下文中会很有用,但它远未普及。

如果您只想使用此库来完成工作,请直接转到#快速参考部分,并将#特性部分作为进一步参考。

背景

市面上已经存在各种测量单位库,但不知何故,它们的使用似乎并不流行。说实话,我只是匆匆瞥了一眼,因为我并不真的需要一个单位库。我只是想写一个,其动机是对使用编译时模板技术来创建更丰富、更智能的数据类型感兴趣。当然,我所写的正是我自己需要时会使用的那个,同时也考虑到了其他人可能使用的潜力。

与许多单位系统一样,它的核心是Barton 和 Nackman 的量纲分析方法,由 Scott Meyers 在

http://se.ethz.ch/~meyer/publications/OTHERS/scott_meyers/dimensions.pdf

描述。这是基于代表长度、质量和时间等基本维度的模板参数。

template<int Length, int Mass, int Time> class unit 
{       
double value;     
 ....
};   

因此

typedef  unit<1, 0, 0> LENGTH; 
typedef  unit<0, 1, 0> MASS; 
typedef  unit<0, 0, 1> TIME; 

typedef  unit<1, 0, -1> VELOCITY;  // LENGTH/TIME = <1, 0, 0> - <0, 0, 1>
typedef  unit<1, 0, -2> ACCERATION;    // VELOCITY/TIME = <1, 0, -1> - <0, 0, 1> 
typedef  unit<1, 1, -2> FORCE;    // ACCERATION*MASS = <1, 0, -2> - <0, 1, 0> 

这使得可以检查赋值、比较、加法和减法只能在具有相同维度的单位之间进行,并且乘法和除法产生正确类型的单位。

这种方法最基本的实现具有优雅的简洁性

LENGTH metres(1); 
LENGTH Kms(1000);  

LENGTH m = 5*metres; //m is implicitly metres and its value is 5
LENGTH k = 2*Kms; //k is implicitly metres and its value is 2000 

但它在使用维度(例如 LENGTH)作为数据类型(具有隐含的未声明的米单位)和变量作为单位大小时略显奇怪。它不是直接的测量单位作为数据类型。大多数测量单位库提供了对这种方法更复杂的封装,以克服这些和其他限制。

以下描述了此库中构建的功能。

特性

首先,为了说明该系统不会让您陷入比您的实际情况所要求的更多的复杂性,如果您的唯一要求是处理长度、面积和体积,那么您的单位定义可以像这样简单:

ulib_Dimension(1, LENGTH)
ulib_base_Unit(LENGTH, metres)

这将使您能够在可执行代码中编写

metres length=3; 
metres width=4; 
metres height=5; 
sq_metres area= length* width; 
cubic_metres volume=  area *  height;

您可能还想使用其他长度单位,您可以将它们定义为已定义的米的标量

ulib_scaled_Unit(Kms,  =, 1000   , metres)
ulib_scaled_Unit(cms,  =, 1/100  , metres)
ulib_scaled_Unit(feet, =, 0.3048 , metres)

然后,在您的可执行代码中,您可以编写:

​metres m=1500;
Kms k=m;  //k will have a value of 1.5 (Kms) and m will have a value of 1500 (metres)
double ratio=k/m;    //ratio will have a scalar value of 1 = 1.5 (Kms)/1500 (metres)

metresKms 之间的缩放是自动和隐式的,并遵循守恒量的规则。也就是说,如果一个声明为 metres 且值为 1500 的变量直接赋值给一个声明为 Kms 的变量,那么 Kms 变量将设置为 1.5(相同数量)。转换因子不再需要出现在您的代码中,甚至不需要任何表示应执行转换的指示。

定义单位

定义单位的工作完全通过以下宏来完成。

首先,最多可以命名和使用 7 个维度。有一个久已建立的习惯,使用三个维度并将其命名为长度、质量和时间。其他维度也是可能的,但不太受欢迎。

ulib_Dimension(1, LENGTH)
ulib_Dimension(2, MASS)
ulib_Dimension(3, TIME)

然后可以使用这些命名的维度来定义基本单位。在这种情况下,我们将使用 MKS 的基本单位:米、千克和秒。

ulib_base_Unit( LENGTH, metres)
ulib_base_Unit( MASS  , Kgs)
ulib_base_Unit( TIME  , secs)

我们可以根据需要添加缩放的单位

ulib_scaled_Unit( Kms,   =, 1000 , metres)
ulib_scaled_Unit( mins,  =, 60   , secs)
ulib_scaled_Unit( hours, =, 60   , mins)

我们可以通过乘法或除法,从现有单位的二进制组合中构建一系列复合单位。以下是一些常见的未缩放复合单位。

ulib_compound_Unit( metres_per_sec,  =, metres,          Div,   secs)   //VELOCITY
ulib_compound_Unit( metres_per_sec2, =, metres_per_sec,  Div,   secs)   //ACCELERATION
ulib_compound_Unit( Newtons,         =, metres_per_sec2, Mult,  Kgs)    //FORCE
ulib_compound_Unit( Joules,          =, Newtons,         Mult,  metres) //ENERGY
ulib_compound_Unit( Watts,           =, Joules,          Div,   secs)   //POWER

我们也可以从缩放的单位创建复合单位。

ulib_compound_Unit( Kms_phour,       =, Kms,             Div,   hours)  //VELOCITY

以及从复合单位创建缩放单位。

ulib_scaled_Unit( Kg_force, =, 9.806649999980076 , Newtons)

我们还可以将一个单位定义为另一个单位的倒数(在第二个版本中添加)。

ulib_Unit_as_inverse_of( Herz, secs)

声明

一旦您定义了一个单位(例如 metres),您就可以使用其名称声明变量。

metres length;

其内置的平方和立方形式。

sq_metres area;
cubic_metres volume;  

其倒数。

inverse_of<metres>::type closeness; 

或提高到任何整数幂。

to_power<metres, 4>::type variance_of_area; 

::type 后缀是适当的提醒,inverse_ofto_power 是单位声明的类型修改器(一种需要新表达式的新概念)。它们不对数字执行任何操作。它们在运行时不做任何事情。

在所有情况下,单位之间以及单位上的可用操作是:

然后,当你开始迭代 2(这是构建迭代的开始)时,你可能想要复制测试用例并将它们重新分类到迭代 2。这还允许对测试用例进行粒度跟踪,并允许你说某个测试用例在一个迭代中是准备好的,但在另一个迭代中不是。同样,如何做到这一点取决于你以及你希望如何报告。 “场景”部分提供了更多细节。

  • 来自相同类型的单位。
metres m=metres(1500);
  • 来自相同维度的单位。
 metres m=Kms(1.5);
  • 来自一个数字。
metres m=1500; 

赋值 = , 加法和减法 + - += -= , 小于和大于比较 < >,

  • 按相同类型的单位。
metres m = metres(1500);
m += metres(50);
if( m > metres(1350); 
     ;
  • 按相同维度的单位。
metres m = Kms(1.5);
m += Kms(0.050);
if( m > Kms(1.35); 
     ;

乘法和除法 * /

  • 按任何单位。
metres_psec V = metres(500) / secs(10); 
Joules Energy= Newtons(5) *  metres(10); 
  • 按或与一个数字。
metres m = 2*metres(50)*5; 
metres m = metres(50) / 5;  
inverse_of<metres>::type closeness= 5 /  metres(50) ; 

提取值作为双精度浮点数 variable.as<unit_type>() 或 as<unit_type>(variable)

防止在声明时错误地将带有错误单位的数值赋值给单位变量,以及在读取其数值时误判其单位。提供的保护是“所见即所得”。您只能在声明时将数值赋值给单位变量,此时您可以看到正在处理的单位类型。

metres m = 5; //Ok  
.........
m = 15; //will not compile 
m = metres(15); //Ok  

并且您只能通过使用 variable.as<unit_type>() 点方法或 as<unit_type>(variable) 自由函数来提取数值(double 类型),这些函数要求您显式指定所需变量的单位。

double dist = m:          //will not compile - no implicit conversion
double dist_in_metres = m.as<metres>();     //ok
double dist_in_kilometres = as<Kms>(m);     //also ok, m will be converted to Kms
double mass_in_kilogrammes = as<Kgs>(m);   //will not compile - m is not a mass

返回表示正整数根的单位变量
variable.integer_root<root_function, positive integer>() 
或 integer_root<root_function, positive integer>(variable)

返回一个单位变量,其单位适合整数根。仅当源单位类型是整数倍数时,才会编译成功。
需要将执行数值根运算的函数(接受 double,返回 double)作为模板参数传递。

sq_metres area =  1600;
metres side_of_square = area.integer_root<sqrt, 2>(); 

根据给定的容差测试近似相等。
variable1.is_approx<tolerance_unit, numerator, denominator>(variable2) .

运算符 ==(精确相等)对于测量量在实践中没有用,因此未实现。取而代之的是,提供了一个点方法来测试近似相等(根据给定的容差)。模板参数指定容差的单位、分子和分母。以下是等效的:

if(m.is_approx<metres, 1, 100>(metres(1550))
;

if(m.is_approx<cms, 1, 1>(metres(1550))
;

if(m.is_approx<mms, 10, 1>(Kms(1.55))
;

带余数的除法 - int n=variable1.goes_into(variable2, Remainder) 

这可能是在许多情况下实践所需。单位必须正确匹配,包括通过引用传递以填充的 Remainder 变量。

运算符 ++ 和 -- 未实现,因为它们对于纯粹的数量没有意义。它们的解释取决于用于测量该数量的单位。如果您想要它们的功能,您必须编写...

metres m=5:
m+=metres(1);  //++
m-=metres(1);   //--

...这迫使您明确您要对数量进行的更改所涉及的单位。

该系统被设计为支持由这些操作组成的任何复杂程度的表达式。

 

出于所述原因,以下全局函数已从库中弃用。

  • 返回具有适当单位类型的变量的平方根 - Sqrt(variable)

作为现有函数integer_root<sqrt, 2>(variable) 的特定实现,它引入了外部依赖项(来自 math.h 的 sqrt),它不属于库本身,因此被弃用为外部选项。

  • approx_equal<tolerance_unit, numerator, denominator>(variable1, variable2)  - is_approx 方法的全局等效项。
  • how_many_in(variable1, variable2, Remainder)  - goes_into 方法的全局等效项。 

这些全局函数具有超出库范围的通用性,因为它们几乎可以与任何数值数据类型一起正常工作。这可能很有用,但由于其相同的性质和未加扰的名称,存在与其他库冲突的风险。因此,它们不能是库的隐式部分,并被弃用为外部选项。

可以使用 ulib_using_depreciated(function_name) 宏单独重新激活这些已弃用的函数。

ulib_using_depreciated(Sqrt) 
ulib_using_depreciated(approx_equal) 

 

多个无因子基单位系统(例如 MKS 和 cgs)

如果您将基本单位定义为 MKS(米、千克和秒),并将厘米定义为米的 1/100,将克定义为千克的 1/1000,那么您代码中用 CGS 单位表示的任何部分都将计算正确并无缝缩放。但是,令人恼火的是,所有中间计算都将隐藏地转换为 MKS 单位,然后再转换回来。出于这个原因,本库将同时支持多个无因子基单位系统(例如 MKS 和 cgs),以便在处理米、千克和秒以及厘米、克和秒时,中间计算可以无因子。这因单位(例如本例中的秒)在不同无因子基单位系统中是共有的而变得复杂,但对于共同单位在所有使用的基系统中都是共有的情况(实际上,很难人为地创造一种它们不共有的情况),这个问题已经解决了。

ulib_Dimension(1, LENGTH)
ulib_Dimension(2, MASS)
ulib_Dimension(3, TIME)

ulib_multiple_Bases(2)

ulib_all_bases_Unit(TIME, secs)

ulib_base1_Unit(LENGTH, metres)
ulib_base1_Unit(MASS,   Kgs)

ulib_base2_Unit(LENGTH, cms,   =, 0.01  , metres)
ulib_base2_Unit(MASS,   grams, =, 0.001 , Kgs)

MKS 复合单位(米/秒等)现在可以像以前一样定义,但 CGS 系统固有的或与之相关的单位现在可以相对于其基本单位定义。

ulib_compound_Unit( cms_per_sec,  =, cms,          Div,  secs)   //Velocity
ulib_compound_Unit( cms_per_sec2, =, cms_per_sec,  Div,  secs)   //Acceleration
ulib_compound_Unit( Dynes,        =, cms_per_sec2, Mult, grams)  //Force
ulib_compound_Unit( Ergs,         =, Dynes,        Mult, cms)    //Energy  

现在您可以声明不同的单位并将它们混合在复杂的表达式中。它将识别哪些单位源自哪个基本系统,并根据每个子表达式中占主导地位的系统调整其工作系统。

Newtons sum_forces = Newtons(4)+Kgs(1)*metres(2)/sq_secs(4)+Dynes(50)+grams(500)*cms(20)/sq_secs(3) ;

它将使用 MKS 作为表达式第一部分的基准,并使用 CGS 作为第二部分的基准,执行它们之间的所有必要转换。请记住,这些决策是在编译时做出的,甚至在您执行代码之前。运行时没有 if 和 else 的犹豫。

更多维度和更多基本单位系统。

这是一个将 FPS(英尺、磅、秒)添加到三个基本单位系统,以及一个包含所有三个基本单位系统上弧度单位的第四个维度 ANGLE 的示例。ANGLE 维度用于限定旋转系统,以便扭矩与能量具有不同的维度。这是非标准的(角度被认为是两个长度的比率,因此是维度的溶解),但如果您抛开这种思想上的反对意见,它工作得很好,并且可以防止在处理旋转系统时产生混淆,就像许多工程领域那样。

该库已编码为接受最多 7 个维度,同时支持最多 5 个基本单位系统。

ulib_Dimension(1, LENGTH)
ulib_Dimension(2, MASS)
ulib_Dimension(3, TIME)
ulib_Dimension(4, ANGLE)

ulib_multiple_Bases(3)

ulib_all_bases_Unit(TIME,  secs)
ulib_all_bases_Unit(ANGLE, radians)

ulib_base1_Unit(LENGTH, metres)
ulib_base1_Unit(MASS,   Kgs)

ulib_base2_Unit(LENGTH, cms,    =, 0.01    , metres)
ulib_base2_Unit(MASS,   grams,  =, 0.001   , Kgs)

ulib_base3_Unit(LENGTH, feet,   =, 0.3048  , metres)
ulib_base3_Unit(MASS,   Pounds, =, 0.45359 , Kgs)

可以像 MKS 和 CGS 一样,通过缩放和复合单位来构建“英尺、磅、秒”单位,只是 FPS_Force 等单位名称可能需要自定义,以表示没有标准名称的未缩放复合单位。

ulib_compound_Unit( feet_per_sec,      =, feet,          Div,  secs)    //Velocity
ulib_compound_Unit( feet_per_sec2,     =, feet_per_sec,  Div,  secs)    //Acceration
ulib_compound_Unit( FPS_Force,         =, feet_per_sec2, Mult, Pounds)  //Force
ulib_compound_Unit( FPS_Energy,        =, FPS_Force,     Mult, feet)    //Energy

ulib_scaled_Unit( Pounds_Force, =, 3.217404867821228, FPS_Force)      //Force
ulib_compound_Unit( Feet_Pounds_Force, =, PoundsForce,   Mult, feet)    //Energy

使用 ANGLE 来打破扭矩和能量之间的明显同一性。

我们将使用更符合逻辑的 MKS 系统来研究添加 ANGLE 维度(以弧度为单位)如何解决能量和扭矩之间的混淆(和出错的可能性)。混淆源于能量被定义为力在力作用方向上移动某物所做的功,而扭矩被定义为在距旋转中心一定距离处施加的力的转向效应。两个距离扮演的角色不同。第一个沿力的方向,是力的结果。第二个垂直于力,并决定力的作用点。我们需要径向米在尺寸上与作用线上的米区分开。

为了解决这个问题,让我们看看旋转系统中的能量。在旋转系统中,只有当扭矩导致它绕角度旋转时,才存在功(能量)。

焦耳 = Nm_torque * 弧度。

如果我们把 ANGLE 作为一个维度,单位是弧度,那么我们将得到扭矩和能量之间的维度区分。

所以,重新排列上面的方程

Nm_torque = 焦耳 / 弧度。

现在,如果我们称我们的径向距离为 radial_metres(给它命名,然后找出它是什么),那么

Nm_torque = 牛顿 * 径向米

将上面的两个方程中的扭矩相等

牛顿 * 径向米 = 焦耳/ 弧度

然后两边都除以牛顿

径向米 = 米 / 弧度

这是关键。我们使用径向米来描述旋转系统上的径向距离,并将其定义为米除以弧度的复合单位。

ulib_compound_Unit( radial_metres, =, metres,  Div,  radians)

然后我们可以正确定义扭矩。

ulib_compound_Unit( Nm_torque,     =, Newtons, Mult, radial_metres)

现在扭矩在尺寸上与能量不同,并且与能量有连贯的关系。

Joules WorkDone = Nm_torque (200)*radians(6.28);

这还提供了角位移与其在半径上产生的弧长之间的连贯关系。

metres arclength = radial_metres(5)*radians(3.14);

优化单位之间的缩放。

单位之间的缩放使用您提供的缩放比例进行。定义单位的方式确保总是可以在任何两个已定义单位之间自动缩放,但在某些情况下,每次缩放都可能通过这些缩放比例的组合来完成。当两个单位都不是基本单位,并且没有明确定义为彼此的比例,或者它们源自不同的基本系统时,就会发生这种情况。这是由于传统编译器如何排列 const double 的初始化方式存在限制。

通过提供一个显式的关系来说明应该这样做,可以将这些更遥远的单位到单位的缩放关系压缩为一个单一的预计算乘法。

ulib_Precalc_unit_to_unit(Kms , mms) //forces pre-calculation at load time

Kms dist = mms(2500); //this is now performed with a single multiplication

预计算不会在编译时进行,但会在加载时进行一次。

基于基准的测量

本节是对文章首次发布后收到的一条评论的回应,该评论指出需要提供一个截距,而不是乘法因子,用于某些测量之间的转换,例如从摄氏度到华氏度。必须理解的是:

摄氏度是一种使用摄氏度作为其单位的测量。

定义摄氏度华氏度作为单位以正常方式表示**温度差**没有问题,就像我们使用秒来表示时间间隔一样。温度差之间的转换只需要应用一个因子,这已经提供了。

ulib_Dimension(5,TEMPERATURE)
ulib_base_Unit(TEMPERATURE, degrees_C)
ulib_scaled_Unit(degrees_F, =, 0.5555555, degrees_C)

它们还可以用来构成复合单位……

ulib_compound_Unit( degrees_C_per_metre,   =,  degrees_C,     Div,   metres) 
ulib_compound_Unit( degrees_C_psec,        =,  degrees_C,     Div,   secs) 

摄氏度华氏温度的问题在于,它们并不是真正意义上的数量或金额的测量。它们是参考标度上的点,每个标度使用不同的物理基准作为零。这有两个后果:

  • 在转换一个测量到另一个测量时需要应用一个偏移量,这意味着它们必须被声明为与仅需要通过因子进行转换的温度差的摄氏度或华氏度区分开。
  • 零不代表不存在(无影响)意味着它们几乎从不直接作为数学公式的因子使用。为了说明这样做的荒谬性;如果您碰巧达到了任意的零基准值,整个项就会归零,并且根据您使用的是摄氏度还是华氏度,结果会不同。

尽管如此,温度是您进行的测量,摄氏度和华氏度是常见的参考标度。许多公式以温度作为输入,并在内部处理它们工作的温度差。因此,迫切需要找到一种连贯的方法来包含它们。

就是这个

ulib_datum_Measurement( Celcius, degrees_C, 0)
ulib_datum_Measurement( Fahrenheit, degrees_F, 32)

基准测量必须基于现有单位,并且必须指定其在所有维度基准测量的共同物理基准值(在本例中为水的冰点)。

基准测量必然比正常数量单位受到更多限制。它们旨在存储基于基准的测量,并正确地在基于不同基准和标度的测量之间进行转换,例如摄氏度和华氏度……

Celcius temperature_C1=30;
Fahrenheit temperature_F1=temperature_C1;

……并通过添加或减去相同维度的数量单位来进行修改:

temperature_C1 += degrees_C(10);

……并产生一个数量单位作为两个基准测量值之差的结果……

Celcius temperature_C2=50;
degrees_C temperature_difference_C = C2 - C1;

//or equally
temperature_difference_C = C2 - F1;
degreesF temperature_difference_F = C2 - F1;

基准测量类型不能用于定义复合单位,基准测量值不能直接用作数学公式的因子。基准测量与它所基于的数量单位之间也没有转换,除了上面描述的通过差值进行转换。

唯一不受这些限制约束的例外是绝对基准测量。

ulib_absolute_datum_Measurement(Kelvin, degrees_C, 273)

开尔文是温度的一种表达方式,与摄氏度和华氏度相同,但基准不同。因此,它必须被定义为一个基准测量,特别是如果我们希望与摄氏度和华氏度进行正确的自动转换。然而,它的零基准(绝对零度)确实代表了不存在,因此其数值确实代表了真实的数量,并像这样在主流热力学中使用。因此,绝对基准测量可以用来构成复合单位……

ulib_compound_Unit( Kelvin_Kgs, =,  Kgs,  Mult,   Kelvin)

……并且绝对基准测量变量可以直接用作数学公式的因子,前提是它们后面加上 ()

kelvin abs_temp=200;
Kgs mass=5;

Kelvin_Kgs KKgs =abs_temp() * mass;

新的基准测量类型是经过深思熟虑后开发的,以考虑它们在其他维度中的应用。该库应该以通用方式包含它所需的一切。我可以想到另外两个例子:

  • 日期和时间 - 当我们将秒定义为 TIME 维度的单位时,我们实际上指的是时间间隔而不是时间点。如果我们想存储和使用日期时间,那么我们必须将其定义为基准测量,其中基督教、穆斯林、中国和计算机制造商的版本都使用不同的实际时间点作为它们的零基准。注意:只有宇宙学家才能开始谈论绝对时间或时间的非存在,虽然他们可能对最初几秒发生的事情很确定,但我认为他们无法精确地说出那是什么时候。所以,时间维度中的绝对基准测量可能并不真正可行。
  • 高度(如海拔) - 有时为了方便起见,可以参照参考标度记录高度,并将其输入到公式中,公式将内部计算它所处理的高度差。您可能希望分别记录相对于地面某一点的高度、相对于海平面的高度或相对于地心的高度。我们可以为它们中的每一个使用基准测量类型,这些类型定义了它们之间的关系,并确保正确执行转换,但相对于地心的高度具有成为绝对基准测量的优势。零相对于地心的高度确实代表了绝对的高度不存在,并且它是可以在最一般意义上直接用作几何公式因子的测量值。  

 

快速参考

该库(即单位定义引擎)可以通过包含一个单一的头文件(包含在下载中)即可使用,无需任何依赖。

#include "ulib.h"

库的大部分内容都包含在一个私有命名空间中,您永远不必知道它。您对它的访问是看不见的,并通过用于定义单位的宏来完成。您可能希望将定义的单位封装在命名空间中,但如果您这样做,请确保 ulib.h 包含在该命名空间内。

namespace myunits{
#include "ulib.h"
#include "myunits.h"  //This is where you define your units
}

 

定义单位(数量)的宏是:

ulib_Dimension(1, LENGTH)

ulib_base_Unit(LENGTH, metres)

ulib_compound_Unit( metres_psec,    =, metres,            Div,    secs)

ulib_Unit_as_inverse_of( Herz, secs )

ulib_scaled_Unit(Kms,        =,    1000,        metres)

ulib_precalc_Unit_to_Unit(feet_pmin, Kms_psec)

以及用于多个基本单位系统

ulib_multiple_Bases(2)

ulib_all_bases_Unit(TIME, secs)

ulib_base2_Unit(LENGTH, cms, =, 0.01, metres)

//N.B. ulib_base_unit and ulib_base1_unit are equivalent.

单位声明的类型修改器是:

sq_metres
cubic_metres
to_power<secs, 4>::type
inverse_of<secs>::type

单位类型变量的点方法是:

double variable.as<unit_type> ()
unit_type variable.integer_root<root_function, positive integer>()
bool variable.is_approx<tolerance_unit, numerator, denominator>(variable2)
int n=variable1.goes_into(variable2, Remainder) 

命名的全局函数是:

double as<unit_type> (unit_variable)
root_unit_type integer_root<root_function, positive integer>(unit_variable)

定义基准测量的宏 (相对于基准的参考点)是:

ulib_datum_Measurement(celcius, degrees_c, 0)

ulib_absolute_datum_Measurement(kelvin, degrees_c, 273)

通过后缀 ()将绝对基准测量变量提升为普通数量单位

kelvin abs_temp=200;
Kgs mass=5;
//then
Kelvin_Kgs KKgs =abs_temp() * mass;
abs_temp() = KKgs / mass;
//also
KKgs = kelvin(200)()  * mass;

已从库中弃用的全局函数 是:

root_unit_type Sqrt(unit_variable)
bool approx_equal<tolerance_unit, numerator, denominator>(numeric_variable1, numeric_variable2)
int how_many_in(U const& D, U2 const& N, U3 & R)

这些可以使用 ulib_using_depreciated(function_name) 宏激活。

量纲分析的封装

Barton 和 Nackman 的量纲分析方法(前面提到过)被 struct dimensions 封装,它没有数据成员,因为它仅涉及编译时的类型形成和类型匹配:

template <int d1, int d2, int d3, int d4, int d5, int d6, int d7>
    struct dimensions 
    {
        enum {    D1=d1,    D2=d2,    D3=d3,    D4=d4,  D5=d5,  D6=d6,    D7=d7    };
    };

enum 用作声明 static const int 成员列表并进行初始化的便捷方法。在这种情况下,它们记录定义类的整数模板参数。作为 enum,它们不分配内存作为变量,而是简单地插入到运行时代码的任何位置,在这种情况下,什么都没有。因此,它们充当纯粹的编译时常量。

为了处理这个问题,提供了结构体,其中包含 dimensions 结构体的 typedefs,代表其他 dimensions 结构体的运算和组合。

  • 显式的单一维度。
template <int num> struct single_dimension
{
    typedef dimensions<(1==num)?1:0, (2==num)?1:0, (3==num)?1:0, 
    (4==num)?1:0, (5==num)?1:0, (6==num)?1:0, (7==num)?1:0> 
        dims;
}; 

//usage pattern
single_dimension<1>::dims LENGTH;
  • 现有 dimensions 结构体的幂。
template<class dims_in, int P> struct dims_to_power
{
    typedef dimensions<dims_in::D1*P, dims_in::D2*P, dims_in::D3*P, 
            dims_in::D4*P, dims_in::D5*P, dims_in::D6*P, dims_in::D7*P> 
        dims;
};
  • 一个单位与另一个单位的乘法(op=Mult)和除法(op=Div)
//general definition
template<class t, class op, class b> struct Combine_dims;

struct Div{}; //Empty classes used only for selecting specialisation
struct Mult{};

//specialisation for multiplication - sum of dimension indices
template<class dims1, class dims2> struct Combine_dims<dims1, Mult, dims2>
{
    typedef dimensions<
        dims1::D1 + dims2::D1, dims1::D2 + dims2::D2, 
        dims1::D3 + dims2::D3, dims1::D4 + dims2::D4, 
        dims1::D5 + dims2::D5, dims1::D6 + dims2::D6, 
        dims1::D7 + dims2::D7> 
                    dims;
};

//specialisation for division - difference of dimension indices
template<class dims1, class dims2> struct Combine_dims<dims1, Div, dims2>
{
    typedef dimensions<
        dims1::D1 - dims2::D1, dims1::D2 - dims2::D2, 
        dims1::D3 - dims2::D3, dims1::D4 - dims2::D4, 
        dims1::D5 - dims2::D5, dims1::D6 - dims2::D6, 
        dims1::D7 - dims2::D7> 
                    dims;
};
  • 整数幂单位类型的整数根。无法用基本维度整数幂表示的结果将产生编译时错误:
template<class dims_in, int R> struct integer_root_of_dims
{
private:
    /*..................................................................
    struct div_exact  - Divides one integer by another 
    but produces an error if they don't divide exactly. 
    ..................................................................*/
    template<int D, int D2, int N> struct div_if_first_param_zero
    { private: enum{ res= "error_unit_is_not_raised_to_this_power"} ;  };

    template<int D, int N> struct div_if_first_param_zero<0, D, N>
    {    enum{ res= D/N};  };

    template<int D, int N> struct div_exact : 
        public div_if_first_param_zero<(D/N)*N - D, D, N> {};

public:
    typedef dimensions< div_exact<dims_in::D1, R>::res, 
                        div_exact<dims_in::D2, R>::res, 
                        div_exact<dims_in::D3, R>::res, 
                        div_exact<dims_in::D4, R>::res, 
                        div_exact<dims_in::D5, R>::res, 
                        div_exact<dims_in::D6, R>::res,
                        div_exact<dims_in::D7, R>::res > 
                dims;
};
 
这些结构体(除了持有 typedef 之外什么都不做)提供了量纲分析的工具。这允许 dimensions 结构体被形成、运算、组合和比较。它们的存在以及它们之间的所有运算都是纯粹的编译时运算。
 

架构 (Architecture of a unit type)

单位类型的基础架构在由基本单位定义宏调用的私有宏中得到说明。

#define ULIB_BASE_UNIT(sys, dimension, name)                                            \
                                                                                        \
    struct  ULIB_DESC_CLASS(name) : public dimension                                    \
    {                                                                                   \
        enum { Scaled=ung::_UNSCALED, System=sys };                                     \
        template <int P> inline static double Base2This(double val)    {return val;}    \
        template <int P> inline static double This2Base(double val)    {return val;}    \
    };                                                                                  \
                                                                                        \
    typedef ung::named_unit<ULIB_DESC_CLASS(name),1> name;                              \
    typedef ung::named_unit<ULIB_DESC_CLASS(name),2> sq_##name;                         \
    typedef ung::named_unit<ULIB_DESC_CLASS(name),3> cubic_##name;
ULIB_DESC_CLASS(name) 宏只是在您传入的名称前添加一个下划线,用作隐藏描述结构体的名称。描述结构体继承您传入的 dimensions 结构体,设置枚举以指示它是否被缩放以及它使用哪个基本单位系统,并提供两个函数,其通用目的是将任何单位转换为其基本单位并返回。在这种情况下,我们正在定义一个基本单位,因此这些函数仅返回传入的值 - 它们不起任何作用。即使没有 inline 修饰符,编译器也能很好地识别这种简单的无效果情况,并且该函数甚至不会被调用。
 
您传入的单位名称成为 named_unit<> 的 typedef,并将描述结构体作为模板参数传递。在这种情况下,metres 将扩展为:
 
#define ULIB_BASE_UNIT(0, LENGTH, metres)                                               \
                                                                                        \
    struct  _metres : public LENGTH                                                     \
    {                                                                                   \
        enum { Scaled=ung::_UNSCALED, System=0 };                                       \
        template <int P> inline static double Base2This(double val)    {return val;}    \
        template <int P> inline static double This2Base(double val)    {return val;}    \
    };                                                                                  \
                                                                                        \
    typedef ung::named_unit<_metres,1> metres;                                          \
    typedef ung::named_unit<_metres,2> sq_metres;                                       \
    typedef ung::named_unit<_metres,3> cubic_metres;
 
当您声明一个单位类型(metres)时,您实际上是在声明一个 named_unit<> 类,该类将单位描述类(_metres)作为模板参数传递。正是**named_unit<> **模板类封装了您声明的单位类型的通用行为。
template <
            class T, int P=1, 
            class dims=typename dims_to_power<T, P>::dims, 
            int Sys=T::System
            > 
    class named_unit
    {
 
named_unit<> 模板类将单位描述类 T 和幂 P 作为模板参数,并从中为模板参数 dims(使用上面描述的 dims_to_power 类型修改器)和 Sys 创建默认值。正是这个类决定了单位之间的操作是否可以进行以及结果单位类型的维度。但是,数值计算的执行主要委托给结构体的特化:Unit2Unit<>Base2Base<>,它们执行任何必要的转换。
 
例如,以下方法允许从另一个 named_unit 构建一个 named_unit:
 
template <class T1, int P1, int Sys2> //Construction
        inline named_unit::named_unit(named_unit<T1,P1, dims, Sys2> const& S)
            {val=Unit2Unit<T1, T, P1, P>::Convert(S.Value());} 
 
这将接受具有任何描述类 T1、任何幂 P1 和任何基本单位系统 Sys2 的命名单位,但维度模板参数必须与其自身的维度模板参数 dims 匹配。在确保此操作仅在单位具有匹配维度时才允许之后,实际赋值由 Unit2Unit<T1, T, P1, P>::Convert(S.Value()) 执行。在许多情况下,例如通过相同类型的单位赋值,Unit2Unit<T1, T, P1, P>::Convert(S.Value()) 不需要执行任何操作,什么也不会被执行 - 也就是说,不会生成任何代码来执行任何操作。当 Unit2Unit 的通用形式……
 
template <class T1, class T2, int P1=1, int P2=1>
    struct Unit2Unit
    { 
        inline static double Convert(double val)    
        {
            return T2::Base2This<P2>
                (
                    Base2BaseByUnit<typename dims_to_power<T2, P2>::dims, T1, T2>
                    ::Conv
                    (
                        T1::This2Base<P1>(val)
                    )
                );
        }
    };
 
……遇到 This2BaseBase2BaseBase2This 的特化时,这些特化什么都不做。例如,基本单位之间(包括未缩放的复合单位)的操作,在相同基本单位系统的情况下,会发现单位的 Base2ThisThis2Base 方法什么都不做,并且还存在一个 Base2Base 的特化,用于两个基数相同的情况,这也什么都不做。结果是编译器会清楚 Convert 没有效果,因此不会生成任何代码,甚至不会生成调用。当然,编译器会完成所有这些以确定不需要编译的内容 - 它不会因此崩溃,没有深度递归。
 
在确实需要进行转换的情况下,Unit2Unit<> 的通用形式仅编译必要转换部分的代码。但是,在不同基本单位系统的复合单位之间进行转换时,这可能涉及一系列乘法。
 
Base2Base<> 的通用形式是:
 
template <class dims, int Sys1, int Sys2> struct Base2Base
    {
    inline static double Conv(double val)
        {return 
                BaseShifter<dims::D1, 1, Sys1, Sys2>::Get(
                BaseShifter<dims::D2, 2, Sys1, Sys2>::Get(
                BaseShifter<dims::D3, 3, Sys1, Sys2>::Get(
                BaseShifter<dims::D4, 4, Sys1, Sys2>::Get(
                BaseShifter<dims::D5, 5, Sys1, Sys2>::Get(
                BaseShifter<dims::D6, 6, Sys1, Sys2>::Get(
                BaseShifter<dims::D7, 7, Sys1, Sys2>::Get(val)))))));}

    }

其中 BaseShifter<> 的各种特化(每个潜在维度一个)在声明单位时提供。对于所有维度幂 dims::D1 等为零的情况,提供了一个默认特化。

template <int Dimension, int Sys1, int Sys2>
    struct BaseShifter<0, Dimension, Sys1, Sys2>
    { 
        inline static double Get(double v){return v;}
    };

这确保了您甚至没有命名的维度以及转换中不涉及的维度会自动产生没有效果的基本移位器。

尽管有这些效率,但从不同基本单位系统的缩放力单位进行转换可能需要四次乘法。对于任何两个具有相同维度的单位,可以使用宏 ulib_precalc_Unit_to_Unit(unit1 , unit2) 将其减少到一次乘法。

#define ulib_precalc_Unit_to_Unit(unit1, unit2)                                         \
                                                                                        \
    const double ULIB_UNIT2UNIT_RATIO(unit1, unit2)=                                    \
        ULIB_DESC_CLASS(unit2)::Base2This<1>(                                           \
            ung::Base2Base                                                              \
                <typename ung::dims_to_power<ULIB_DESC_CLASS(unit1), 1>::dims,          \
                ULIB_DESC_CLASS(unit1)::System, ULIB_DESC_CLASS(unit2)::System>         \
                    ::Conv(                                                             \
                        ULIB_DESC_CLASS(unit1)::This2Base<1>(1)                         \
                    )                                                                   \
                );                                                                      \
                                                                                        \
    template <int P> struct ung::Unit2Unit                                              \
        <ULIB_DESC_CLASS(unit1), ULIB_DESC_CLASS(unit2), P, P>                          \
    {                                                                                   \
        typedef ung::equal_dimensions                                                   \
            <ULIB_DESC_CLASS(unit1), ULIB_DESC_CLASS(unit2)> check_equal_dimensions;    \
                                                                                        \
        ULIB_DECLARE_POWX(ULIB_UNIT2UNIT_RATIO(name, unit2))                            \
        inline static double Convert(double val)    {return powX<P>()*val;}             \
    };                                                                                  \
    template <int P> struct ung::Unit2Unit                                              \
        <ULIB_DESC_CLASS(unit1), ULIB_DESC_CLASS(name), P, P>                           \
    {                                                                                   \
        ULIB_DECLARE_POWX(ULIB_UNIT2UNIT_RATIO(unit1, unit2It))                         \
        inline static double Convert(double val) {return (double)1/powX<P>()*val;}      \
    };
 

它首先声明一个全局 const double,该变量将在加载时(而不是编译时)初始化,方法是调用与 Unit2Unit<> 相同的调用。它不能调用 Unit2Unit<> 来执行此操作,因为这会实例化该组合的通用形式并破坏目的。然后,它为两个单位之间的每个方向定义 Unit2Unit<> 的特化,该特化直接使用预计算的全局 const double。这两个单位之间的任何转换都将始终找到这些特化,并以一次操作完成转换。

 
大多数对 named_unit<> 的操作将返回相同类型的 named_unit<>,但与另一个单位的乘法或除法将返回具有不同维度但可能与您定义的任何单位不匹配的单位类型。在复杂计算的中间值中尤其如此。因此,需要一种处理未命名单位类型的机制。这是由模板类 x_unit<> 提供的。
 
template <class dims, int Sys> class x_unit
    {
 
x_unit<> 没有名称或单位描述类。其身份仅由其维度(dims)和其基本单位系统(Sys)决定。其单位始终是其自身基本单位系统的单位。
 
named_unit<>x_unit<> 是涉及的唯一数据结构,因为它们有一个 double 类型的成员变量,用于保存它们在其自身单位中表示的数量。它们是将被替换的原始 double 的替代品。两者都必须提供可以与这些原始 double 发生的所有操作。在此区域 named_unit<>x_unit<> 之间存在一些差异:named_unit<> 在您的代码中声明。它们是您可以进行操作的变量,因此它们需要支持 +=-= 和赋值 =。相反,x_unit<> 不仅仅是未命名的类型,它们还是临时的变量,没有名称可以供您操作。您不声明它们,它们在评估您编写的表达式的中间步骤中自动且无形地创建。它们不需要支持 +=-= 和赋值 =,因为作为未命名的临时变量,这些操作不可能被调用。

现在我们可以看看当你用另一个单位类型变量除一个单位类型变量时会发生什么,比如说米除以秒。

metres dist=10;

secs time=5

metres_per_sec velocity = dist / time;

dist 的类型是 metres,它是 named_unit<_metres, 1> 的 typedef,而 time secs 的 typedef,因此 named_unit<> 的运算符 /,它接受另一个 named_unit<>,将被调用。

template <class T1, int P1, class dims2>  // /
inline x_unit<
        typename Combine_dims<dims, Div, dims2>::dims, 
        SysFilter<Sys,T1::System>::sys
        > 
    named_unit::operator / ( named_unit<T1, P1, dims2> b) const 
        {
            return UnitByUnit<T,T1,P,P1>::Divide(Value(), b.Value());
        }

此方法将接受任何名称、任何幂和任何基本单位系统的 named_unit<>。这通过引入新的模板参数来表示……

template <class T1, int P1, class dims2>

……以及它们的不合格应用,以完全满足值参数的类型。

operator / ( named_unit<T1, P1, dims2> b) const  
返回值的单位类型由 Combine_dims 结构体形成,该结构体在上面量纲分析封装中描述。
typename Combine_dims<dims, Div, dims2>::dims 
以及 SysFilter 结构体,该结构体决定返回值的适当基本单位系统(其工作方式将在后面进一步描述)。
 SysFilter<Sys,T1::System>::sys 
实际的数值除法由结构体 UnitByUnit<> 执行。
template <class T1, class T2, int P1=1, int P2=1>
struct UnitByUnit
{
private:
    inline static double OtherToThisBase(double val)
        {
        return Base2BaseByUnit<typename dims_to_power<T2, P2>::dims, T1, T2>
                ::Conv
                (
                    T2::This2Base<P2>(val)
                );
        }
public:    
    inline static double Divide(double v1, double v2)    
        {return T1::This2Base<P1>(v1) / OtherToThisBase(v2);}

    inline static double Multiply(double v1, double v2)
        {return T1::This2Base<P1>(v1) * OtherToThisBase(v2);}
};
这确保所有转换都将执行,以将两个操作数置于左操作数基本单位系统的基本单位中。在这种情况下,两个操作数都位于同一基本单位系统的基本单位中,因此由于无效果的内联函数折叠,对 OtherToThisBase 的调用将在编译中消失,而只会编译 v1/v2
 
运算符 / 重载的返回值将是 x_unit<>
x_unit<
        typename Combine_dims<dims, Div, dims2>::dims, 
        SysFilter<Sys,T1::System>::sys
        > 
那么这个 x_unit<> 接下来会发生什么?它可能会被无形地与其他 x_unit<>named_unit<> 组合,形成更多的 x_unit<>,但最终您必须将结果捕获为 named_unit<> 才能对其进行任何操作,这就是您必须正确处理单位的地方,就像我们编写时那样。
metres_per_sec velocity = dist / time; 
velocity 是一个 named_unit<>,它被传递了 _metres_per_sec 作为描述类,并且它正在从 dist / time 的结果构建,dist / time 将是一个 x_unit<>
 
named_unit<> 有一个接受 x_unit<> 作为参数的构造函数。
template<int Sys2> inline named_unit::named_unit(x_unit<dims, Sys2> const& S)
{
    val=ConvertFromXUnit<Sys2>(S.Value());
}
此构造函数将接受任何基本单位系统的 x_unit<>(新的模板参数 Sys2),但它必须具有与 named_unit<>(模板参数 dims)相同的 dimensions 结构体。如果我们写……
Newtons force = dist / time;   //error will not compile 
……那么我们会收到一个编译错误,告诉我们这是错误的,dist / time 产生的 x_unit<>Newtons 的维度不匹配。
 
named_unit 的值由对私有成员 ConvertFromXUnit 的调用设置……
template<int Sys2>    inline static double ConvertFromXUnit(double val)
{
    return T::Base2This<P>(
        Base2BaseBySys<dims, Sys2, Sys>::Conv(val)
                );
}
……执行任何必要的转换。在这种情况下,metres_per_sec 单位已经处于基本单位,所以 ConvertFromXUnit 将什么也不做,构造函数将编译为:
template<int Sys2> inline named_unit::named_unit(x_unit<dims, Sys2> const& S)
{
    val=S.Value();
}

如果您只使用一个基本单位系统,那么所有系统模板参数(如上面的 Sys2)将始终为零,它们在操作中不会真正起作用。但是,如果您使用多个基本单位系统,那么参数就是区分它们的地方。乍一看,每个基本单位系统由一个不同的数字表示似乎就足够了,但您会使用什么数字来表示像秒这样的单位,这些单位通常被多个基本单位系统共享(例如 MKS 和 CGS)?我选择的解决方案如下:

每个基本单位系统都有一个内部数字用于 int System 模板参数。每个都是 2 的整数幂,以便每个基本单位系统由一个二进制位表示。我们通过将 System 枚举初始化为其所属的基本单位系统的逻辑 OR 来指示一个单位在基本系统之间是不可知的。

我们需要一种方法来确定应该为哪个基本单位系统计算临时变量(x_units<>),并且我们需要认识到 OR 运算的值(表示秒等单位)被所有基本单位系统接受为它们自己的。这是通过模板结构 SysFilter<> 完成的。

template<int Sys1, int Sys2> struct SysFilter 
{
private:
    template<int SysANDSys2, int Sys> struct SysFilterReturn    //general case
    { enum {sys=SysANDSys2}; };

    template<int Sys> struct SysFilterReturn<0, Sys>            //specialisation when 1st param is 0
    { enum {sys=Sys}; };
public:
    enum {sys = SysFilterReturn<Sys1 & Sys2, Sys1>::sys};    
};

SysFilter 传递两个 int Sys 模板参数。如果它们的逻辑 AND 非零,那么它们要么相同,要么其中一个是一个组合基符号(2 个或更多位集,如秒等使用的),它匹配另一个基本系统。在这种情况下,不会改变基本系统,逻辑 AND 确保只传播一个位。但是,如果逻辑 AND 计算为零,那么就改变了基本单位系统,第一个 int Sys 模板参数被 unmodified 地用作新的工作基准。在两个 Sys 模板参数都是组合基符号(例如秒*秒)的情况下,会传播组合基符号的 2 位,但没关系,一旦遇到任何不是基本系统不可知的单位,其中一位就会被移除。SysFilter<> 使用结构 SysFilterReturn<> 来初始化其 sys 枚举,以便将 Sys1 & Sys2Sys1 构造函数传递给它。SysFilterReturn<> 又使用模板特化来提供适当初始化的 sys 枚举。其用法是 SysFilter<Sys1, Sys2>::sys

SysFilter<> 用于任何需要比较或组合两个基本单位系统数值代码(System 枚举)的地方。它的许多用法都封装在 Base2BaseBySys<> & Base2BaseByUnit<> 模板结构中,这些结构用作基到基转换的便捷包装器。

/******************************************************************        
These wrap the use of Base2Base<> providing it with base system parameters that have been processed by 
SysFilter<>. Two versions are provided for convenience in different calling contexts.
*******************************************************************/

template <class dims, int Sys1, int Sys2> struct Base2BaseBySys 
: public Base2Base
<dims, SysFilter<Sys1,Sys2>::sys, 
SysFilter<Sys2,Sys1>::sys>
{};
template <class dims, class T1, class T2> struct Base2BaseByUnit 
: public Base2Base
<dims, SysFilter<T1::System,T2::System>::sys, 
SysFilter<T2::System,T1::System>::sys>
{}; 

 

第二个版本中的代码更改

在其他人的关注和勤奋的帮助下,我对第二个版本进行了一些代码更改。

1. 应用了一些相当直接的局部特化,以泛型方式处理自对自转换,而不是通过宏逐个生成它们。这些包括:

Unit2Unit 的自对自转换 - *修复了添加相同类型单位时的运行时低效率*

template <typename T, int P>
struct Unit2Unit<T, T, P, P>
{
      inline static double Convert(double val)
       {
        return val;
       }
};

Base2Base 的自对自转换 - **修复了仅使用一个基本系统时意外要求包含 **ulib_multiple_Bases(1) 

template <class dims, int Sys> struct ung::Base2Base<dims, Sys, Sys>                        
{
    inline static double Conv(double v)
    {
        return v;
    }
};

以及所有基本单位的特定维度上的任何基到任何基转换 - *替换了大量宏生成。*

#define ulib_all_bases_Unit(dimension, unit)            \
                            \
    ULIB_BASE_UNIT(ulib_OredBases, dimension, unit)    \
    template <int P, int Sys1, int Sys2 >             \
    struct ung::BaseShifter<P, dimension::Num, Sys1, Sys2>    \
        { inline static double Get(double v){return v;} };    \
    template <int Sys1, int Sys2>                \
    struct ung::BaseShifter<0, dimension::Num, Sys1, Sys2>    \
        { inline static double Get(double v){return v;} };    

2. 提供了一个新的单位定义宏,允许将一个单位定义为另一个单位的倒数。

ulib_Unit_as_inverse_of(name, orig)    

这是 ulib_compound_Unit(name, is, t, Operation, b) 的改编,其中第一个项被替换为标量(零维度)。

3. 点方法和自由函数的审查和合理化。

as<>() 函数,旨在强制您在提取单位类型的数值时作为模板指定单位……

/*************************************************************************************
as<>() - function to release the raw double from a unit
**************************************************************************************/
template<class U> 
inline double const& as(U const& nu)
    {return ung::_as<U>(nu);}

……存在两个缺点:

  • 您不必指定模板参数,因为编译器可以推断它。
  • ​它几乎可以接受任何参数,并且由于名称很短,它可能会与其他库发生冲突。

以下替换解决了这两个问题。编译器将只考虑 x_units 和 named_units(还有 datum_units)作为参数,并且必须提供正确的单位类型模板。

/****************************************************************************
as<>() - function to release the raw double from a x_unit
*****************************************************************************/
template <class U, class dims, int Sys> 
inline double const& as(ung::x_unit<dims, Sys> const& nu)
    {return nu.as<U>();}

/***************************************************************************
as<>() - function to release the raw double from a named_unit
****************************************************************************/
template<class U, class T, int P> 
inline double const& as(ung::named_unit<T, P> const& nu)
    {return nu.as<U>();}

asinteger_rootis_approxgoes_into 现在可以作为命名单位和 x_unit 的点方法(datum_units 仅支持 as 和 is_approx 方法)。

Sqrtapprox_equalhow_many_in 全局函数已因库完整性原因弃用

4. 添加了基准测量数据类型。

这在代码中由 datum_unit 类模板表示。它与 named_unit 类模板相似,但功能更有限。

template <
    class D, //description class of datum_unit
    class T, //description class of named_unit on which it is based
    class dims=typename dims_to_power<T, 1>::dims, 
    int Sys=T::System
    > 
class datum_unit

基准测量类型使用 ulib_datum_Measurement(Celcius, degrees_C, 0) 宏定义。

#define ulib_datum_Measurement(name, orig, offset)                \
struct ULIB_DESC_CLASS(name)                        \
{                                    \
    enum {absolute=0};                            \
    static double DoOffset(double val){return val+(offset);}            \
    static double UndoOffset(double val){return val-(offset);}            \
};                                    \                                                            \
typedef ung::datum_unit<ULIB_DESC_CLASS(name), ULIB_DESC_CLASS(orig)> name;    

……它定义了一个用于基准测量的描述类,提供偏移信息,并将该信息和它所基于的 named_unit 的描述类传递给 datum_unit

datum_unit 类模板使用基准测量描述类来计算偏移量,并使用 named_unit 描述类来计算维度适宜性。

绝对基准测量类型使用 ulib_absolute_datum_Measurement(Celcius, degrees_C, 0) 宏定义。

#define ulib_absolute_datum_Measurement(name, orig, offset)                \
struct ULIB_DESC_CLASS(name) : public ULIB_DESC_CLASS(orig)            \
{                                    \
    enum {absolute=1};                            \
    static double DoOffset(double val){return val+(offset);}            \
    static double UndoOffset(double val){return val-(offset);}            \
};                                    \
typedef ung::datum_unit<ULIB_DESC_CLASS(name), ULIB_DESC_CLASS(orig)> name;

这有两个区别:

基准测量描述类继承自 named_unit 描述类。这赋予了它一组维度,允许绝对基准测量用于构成复合单位。

absolute 枚举设置为 1。这将在 datum_unit 类中启用一个 operator()()

/**********************************************************************
If absolute, can be read as a named_unit by appending ()
***********************************************************************/
typename if_true_use_type_else_unknown
    <
        D::absolute,
        named_unit<T, 1, dims, Sys>
    > ::result_type &    operator()()
{
    return reinterpret_cast<named_unit<T, 1, dims, Sys>& >(*this);
}

该函数返回一个引用,该引用被转换为 named_unitif_true_use_type_else_unknown 类模板将 result_type 定义为 named_unit<T, 1, dims, Sys>,如果 D::absolute 为 true,或者定义为未知类,如果 D::absolute 为 false。

最后评论

看起来每个单位类型周围包裹了很多代码,但这一切都是编译时记账。编译出来的只是双精度浮点数以及您在代码中编写的它们的组合 - 就好像所有单位都声明为双精度浮点数一样。唯一生成的额外代码是自动单位缩放,而这您本应手动编写。

为了阐明该库的编译时性质:只有数据需要运行时存储,只有执行某些操作的函数和方法才会生成运行时代码。仅由枚举和 typedef 组成的结构体和类,以及被调用但没有效果的函数和方法,根本没有运行时存在。如果您使用缩放单位,则会在运行时分配内存来存储所有涉及的转换因子,并在加载时执行它们的初始化。这些缩放因子的应用当然是无论如何都必须存在的代码。

要完全理解一切如何协同工作,您需要查看源代码。它相当易读且注释良好。

历史

第一个版本 - 2014 年 9 月

第二个版本 - 2014 年 12 月

  • 修复 - 在仅使用一个基本单位系统时,意外要求包含  ulib_multiple_Bases(1)。 
  • 修复 - 添加相同类型单位时意外的运行时低效率。 
  • 修复 - 没有简单的方法可以将一个单位定义为另一个单位的倒数。 现在提供了一个新的 ulib_Unit_as_inverse_of( Herz, secs)  单位定义宏用于此目的。
  • 修复 - 允许在不提供单位类型作为模板参数的情况下使用 as<>() 自由函数。 
  • 修复 - as<>() 现在也可以作为单位类型变量的点方法使用。
  • 修复 - 已弃用引入依赖项或可能与其他库冲突的全局函数。
  • 增强 - 添加了 基准测量 用于测量,例如摄氏度和华氏度温度,其数值不代表数量或金额,而是参考标度上的一个点,其零点是任意的。 
    有关第二个版本中代码更改的评论在此处收集。.

 

© . All rights reserved.