【问题标题】:Units for types in C++C++ 中的类型单位
【发布时间】:2018-08-21 07:14:07
【问题描述】:

C++ Core Guidlines P.1 change_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++?

【问题讨论】:

  • 也许这就是你所说的:它不会抛出错误,也不会编译,因为change_speed 采用的是速度而不是浮点文字。

标签: c++ cpp-core-guidelines


【解决方案1】:

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

/* "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;
}

正如 cmets 中提到的 @KillzoneKid,需要 C++11 才能使其工作。

【讨论】:

    【解决方案2】:

    您的代码中有两个不同的东西:

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

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

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

    • 您无法像在现实生活中那样在表示持续时间和长度的类型之间进行转换。
    • 在表示同一种事物的类型中(例如,secondshours),如果您失去精度,则没有隐式转换,例如,您不能将 seconds 转换为 hours,除非您代表我们的浮点类型 (float / double)。
    • (隐式和非隐式)转换为您处理缩放:您可以将 hours 转换为 seconds,而无需手动乘以 3600。
    • 您可以提供实际的运算符来为您处理转换,例如,在您的示例中,长度和持续时间之间的除法运算符可以提供速度。根据长度类型和持续时间类型自动推断出确切的速度类型:
    auto speed = 70km / 1h; // 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

    “单元”类可以通过以下方式定义:

    • 它所代表的事物类型:时间、长度、重量、速度……
    • 用于表示这些数据的 C++ 类型:intdouble、...
    • 此类型与同一单元的“1-type”之间的比率。

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

    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 类型与一秒(这是选择的基本持续时间)之间的比率:

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

    运算符字面量

    这些是在 C++11 中引入的,是literals operators

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

    【讨论】:

    • 一旦我们有了 C++ 单元,我们就会明白执行量纲分析是多么好的实践,以及使用无量纲方程计算模拟是多么好的解决方案! ;)
    • @Oliv 在 C++ 中实现单元已经很容易了,特别是如果你只是包装了一个 std::chrono::duration。 ;)
    • 缺少维度的概念(单位模板参数),以及不同维度的乘/除数字的可能性:1米/1秒=1米/秒=>维度米/秒=速度。见增强单位。
    猜你喜欢
    • 2018-09-29
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    • 2011-05-07
    • 2013-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多