C++中类型的单位

Units for types in C++

本文关键字:单位 类型 C++      更新时间:2023-10-16

在C++核心指南p.1change_speed示例中,它显示了如下所示使用的Speed类型:

change_speed(Speed s); // better: the meaning of s is specified
// ...
change_speed(2.3); // error: no unit
change_speed(23m / 10s); // meters per second

我对这个例子的最后两行特别感兴趣。第一个似乎表明,如果没有为change_speed提供带参数的单元,它将抛出一个错误。最后一行显示了使用一些ms文字定义的单位。这两个新功能都出现在C++的现代版本中了吗?如果是这样的话,这样的东西将如何实现,需要什么版本的C++?

如注释中所述,核心指南中的示例使用用户定义的文字来构造直观地表示物理量的特定于应用程序的类型。为了说明具体的例子,考虑以下类型:

/* "Strong" speed type, unit is always [m/s]. */
struct Speed {
long double value;
};
/* "Strong" length type, parameterized by a unit as multiples of [m]. */    
template <class Period = std::ratio<1>> struct Length {
unsigned long long value;
};

跟踪Length对象的单位可能没有多大意义,但对于Speed实例来说没有多大的意义,但让我们考虑这里最简单的示例。现在,让我们来看两个用户定义的文字:

#include <ratio>
auto operator ""_m(unsigned long long n)
{
return Length<>{n};
}
auto operator ""_km(unsigned long long n)
{
return Length<std::kilo>{n};
}

它们允许您实例化Length对象,如下所示:

/* We use auto here, because the suffix is so crystal clear: */
const auto lengthInMeter = 23_m;
const auto lengthInKilometer = 23_km;

为了构造一个Speed实例,让我们定义一个合适的运算符来将Length除以duration:

#include <chrono>
template <class LengthRatio, class Rep, class DurationRatio>
auto operator / (const Length<LengthRatio>& lhs,
const std::chrono::duration<Rep, DurationRatio>& rhs)
{
const auto lengthFactor = static_cast<double>(LengthRatio::num)/LengthRatio::den;
const auto rhsInSeconds = std::chrono::duration_cast<std::chrono::seconds>(rhs);
return Speed{lengthFactor*lhs.value/rhsInSeconds.count()};
}

现在,让我们再次看看核心指南中的例子,

void change_speed(const Speed& s)
{
/* Complicated stuff... */
}

但最重要的是,如何调用这样一个函数:

using namespace std::chrono_literals;
int main(int, char **)
{
change_speed(23_m/1s);
change_speed(42_km/3600s);
change_speed(42_km/1h);
return 0;
}

正如@KillzoneKid在评论中提到的那样,这需要C++11。

代码中包含两种不同的东西:

  1. 使用强/单元类型使代码更加健壮,即区分两种整数类型。这在某些语言(例如Ada)中是内置的,但在C++中没有,但您可以创建封装整数类型的类来模仿这种行为(见下文)。

  2. 使用运算符文本以用户友好的方式创建此类实例,即,您编写1s而不是seconds{1}。这只是一个方便功能,在某些地方可能很有用。

使用强整数类型非常有用,因为它使您的代码不太容易出错*:

  • 你不能像在现实生活中那样在表示持续时间和长度的类型之间转换
  • 在表示同类事物的类型(例如,secondshours)中,如果丢失精度,则不会进行隐式转换,例如,除非使用浮点类型(float/double)表示小时,否则无法将seconds转换为hours
  • (隐式和非隐式)转换为您处理缩放:您可以将hours转换为seconds,而无需手动乘以3600
  • 您可以提供实际的运算符来为您处理转换,例如,在您的示例中,长度和持续时间之间有一个除法运算符,可以给出速度。根据长度类型和持续时间类型自动推断出准确的速度类型:
auto speed = 70_km / 1_h; // Don't bother deducing the type of speed, let the compiler do it for you.
  • 单元类型是自记录的:如果一个函数返回microseconds,你知道这是什么,你不必希望记录返回unsigned long long的函数的人提到这代表微秒

*我在这里只谈论隐式转换,当然,您可以显式进行转换,例如,使用duration_cast(精度损失)


机组类型

将整数类型封装在";单位";类一直都是可用的,但是C++11带来了一个标准的封装整数类型:std::chrono::duration

A";单位";类可以定义为:

  • 它所代表的事物类型:时间、长度、重量、速度
  • 它用来表示这些数据的C++类型:intdouble
  • 这种类型与";1-型";同一单元的

目前,标准只提供类似持续时间的类型,但已经就提供更通用的基本单元类型进行了讨论(可能是一项建议?),如:

template <class Unit, class Rep, class Ratio = std::ratio<1>> class unit;

其中Unit将是指示所表示的事物种类的占位符,例如:

struct length_t { };
template <class Rep, class Ratio = std::ratio<1>>
using length = unit<length_t, Rep, Ratio>;

但这还不是标准,所以让我们看看std::chrono::duration:

template <class Rep, class Period = std::ratio<1>> class duration;

Rep模板参数为C++类型:

  • 标准定义的持续时间类型具有整数表示形式(实现定义)
  • 基础类型定义了可以隐式进行的转换类型:
    • 您可以隐式地将整数小时转换为整数秒(乘以3600)
    • 不能隐式将整秒转换为整小时,因为这样会丢失精度
    • 您可以将整数秒转换为double小时

Period模板参数定义duration类型与1秒(选择的基本持续时间)之间的比率:

  • std::ratio是一个非常方便的标准定义类型,它简单地表示两个整数之间的比率,并具有相应的运算(*/…)
  • standad提供多种不同比例的持续时间类型:std::chrono::secondsstd::chrono::minutes

操作员文字

这些已经在C++11中引入,并且是文字运算符。

s是标准的,包含在chrono标准库中:

using namespace std::chrono_literals;
auto one_second = 1s;
auto one_hour = 1h;

m不是标准的,因此应该以_作为前缀(因为它是用户定义的),就像23_m一样。你可以这样定义你自己的运营商:

constexpr auto operator "" _m(unsigned long long ull) { 
return meters{ull};
}