【问题标题】:use typedef for more type safe in c++在 c++ 中使用 typedef 以获得更多类型安全
【发布时间】:2018-01-06 16:23:28
【问题描述】:

我想重新访问post。目前,我正在尝试避免在程序中混合 Rad 和 Degree 类型的角度产生的错误。

例如:

  typedef float Degree;
  typedef float Radian;

  Degree a = 15.;

  Radian b = 3.14/4.;

  float c = a + b;  // no compile error

是否有新的更新解决方案?

编辑 01:

我求助于编写自己的类,希望它的体积小且没有依赖性。这是工作代码

#include <stdio.h>
#include <iostream>

template<typename numT>
class RadAngle {
public:
    RadAngle() {
      AngVal = 0.0;
    }
    RadAngle(const numT& angV) {
      AngVal = angV;
    }
    void operator = (const RadAngle<numT>& ang1) {
      AngVal = ang1.getVal();
    }
    RadAngle operator+(const RadAngle<numT>& ang1) const { return RadAngle<numT>(AngVal+ang1.getVal()); }
    RadAngle operator-(const RadAngle<numT>& ang1) const { return RadAngle<numT>(AngVal-ang1.getVal()); }
    RadAngle operator*(const RadAngle<numT>& ang1) const { return RadAngle<numT>(AngVal*ang1.getVal()); }
    RadAngle operator/(const RadAngle<numT>& ang1) const { return RadAngle<numT>(AngVal/ang1.getVal()); }

    numT getVal() const { return AngVal;};

private:
    numT AngVal;
};


int main() {

   RadAngle<float> a(1.5);
   RadAngle<float> b(3.14);
   RadAngle<float> c = a+b;

   //std::cout << c << std::endl;
//    printf("%.2f",c.getVal());


   return 0;
}

【问题讨论】:

标签: c++ c++11


【解决方案1】:

你所拥有的根本无助于类型安全,除了可能是薄弱的文档。就编译器而言,floatDegreeRadian 是完整的同义词;这称为弱类型别名。强类型别名不是 C++ 的一部分,但您可以解决这个问题。关于这方面的两篇好文章是herehere。基本思想是为强类型定义创建一个通用类模板,并使用它来创建每个单独的别名。

如果您不想自己编写所有样板,我建议您使用第三方库来处理这个问题。我上面链接的帖子的作者都为它编写了库,NamedTypetype_safe。如果您需要更重的东西,您应该查看Boost.Units。请注意,我自己没有使用过这些。它们正是我检查是否需要这些功能的地方。

您没有问过这个问题,但与在任何地方使用 float 并手动跟踪单元相比,这些都不应该有任何运行时性能成本,但可能会使编译速度变慢。

【讨论】:

  • 谢谢@Daniel。我也在考虑使用库而不是从头开始编写它,因为使用类来实现类型安全似乎是不可避免的
【解决方案2】:

最好的办法是为每种测量创建一个类,并实施将一种测量转换为另一种测量的方法。这些类可以/应该有一个共同的超类。

【讨论】:

  • 正如我所提到的,重复所有基本数字运算符并且尚未考虑可能添加的开销是很麻烦的
  • 编写运算符不需要那么长时间,如果您认为这是像 OP 那样的问题,那可能是值得的。
  • 除了运算符 +,-,*,/ 如果我写一个类,还有很多其他的要添加。我对吗?打印
  • 您能否详细说明您的答案?我尝试了 OP 的结构,但在他的示例中似乎仍然存在问题。我不明白他所说的“模板”是什么意思,他没有使用它
【解决方案3】:

我会选择一个作为内部表示(例如 Radian)并将其他的写为包装类

typedef double Radian;
class Degree {
public:
    Degree() {
        m_radian = 0.0;
    }
    Degree(double degree) {
        m_radian = degree / 180.0 * 3.1415926;
    }
    void operator = (double degree) {
        m_radian = degree / 180.0 * 3.1415926;
    }
    operator Radian() const { return m_radian; }
private:
    Radian m_radian;
};

void print_rad(Radian rad) {
   printf("rad: %lf\n", rad);
}

int main() {
   Radian rad = 123.0;
   Degree degree = 456.0;
   print_rad(rad);
   print_rad(degree);
   print_rad(rad + degree);
   return 0;
}

输出:

rad: 123.000000
rad: 7.958701
rad: 130.958701

【讨论】:

  • 几个数字运算符+-*/,打印,类型转换怎么样?一个人必须全部写出来?
  • Radian 是 double 的类型别名,一切都应该自动可用,除非您想要不同的行为,否则您可以编写更复杂的 Radian 类而不是 typedef。
  • 当我混合双精度、弧度或度数时,它仍然不能避免类型混合。编译器不会为 double a = Radian + 3.3; 之类的操作创建错误;甚至弧度 c = rad + degree;
  • 如果你想拒绝使用double,你还需要将Radian 设为一个类,是的,你需要编写运算符,但它很简单,只需要在@ 上做一次987654324@上课。 Degree 将保持简单。
  • 在我们高效的软件中,我们以类似的方式解决了它。在我们的例子中,我创建了转换运算符explicit。如果混合太多度数,值不会变得更好。和拉德。 (我的意思是舍入误差。Pi 就是这样一个数字……)
【解决方案4】:

好吧,您可能想一直使用单位进行计算。

用所有相关的(SI?)基本单位做这样的事情:

template <class T, int meter, int second, int pow_10, int pow_deginrad>
class unit {
    T num = 0;
};
// add convenience typedefs. Also look into `operator ""` for denoting literals.

现在您必须在一个地方定义类型和大小之间的算术。
编码愉快。

【讨论】:

  • 我不建议坚持使用 SI 基地。 SI 认为角度是无量纲的,这在数学上有效,但为了类型安全(以及这个特定示例),您可能希望将它们分开。
  • 当然,这是决定将角度单位声明为弧度(而不是度数,因为这样计算得更好)的正当理由。写下公式时只需注意这一点。
【解决方案5】:

我认为你的方法有点缺陷。

您尝试建模的是物理量 - 具有值和单位的类型。

没有一个量叫做弧度。有一个量叫做角度,其单位可以是弧度或度数。

您需要一些基础设施来处理数量单位的转换以及对单位和值执行操作。

例如,

L1 = 10 "m" (length)
L2 = 20 "m" (length)

L1 * L2 = 200 "m^2" (area)

F1 = 10 "N" (force)
A1 = 2 "m^2" (area)
F1/A1  = 5 "Pa" (pressure)

A2 = 10 "deg"
convert(A2, "rad") = 0.174533 "rad"

如果你能添加处理单位的代码,剩下的功能就很容易了。

在我的工作中,我们不得不这样做,而且代码量不小。在这里深入研究这个主题对我来说没有意义。


潜在的 C++ 代码:

struct Quantity
{
   double value;
   std::string unit;
};

// Defines operations on Quantity.
Quantity operator+(Quantity const& q1, Quantity const& q2) { ... }
Quantity operator-(Quantity const& q1, Quantity const& q2) { ... }
Quantity operator*(Quantity const& q1, Quantity const& q2) { ... }
Quantity operator*(Quantity const& q1, double f) { ... }
Quantity operator/(Quantity const& q1, Quantity const& q2) { ... }
Quantity operator/(Quantity const& q1, double f) { ... }
Quantity convert(Quantity const& q, std::string const& unit) { ... }

auto L1 = Quantity(10, "m");
auto L2 = Quantity(10, "m");

auto a = L1*L2;  // Resulting in Quantity(100, "m^2")

auto F1 = Quantity(10, "N");
auto A1 = Quantity(2, "m^2");
auto p = F1/A1;  // Resulting in Quantity(5, "Pa")

auto A2 = Quantity(10, "deg");
auto A3 = Convert(A2, "rad");  // Resulting in Quantity(0.174533, "rad")

【讨论】:

  • 有什么方法可以制作有效的 C++,或者它是否意味着是伪代码? C++ 用户定义文字通常使用10_m,而不是10 "m",但您需要单独定义每个幂。
  • @DanielH,这甚至不是伪代码。它是概念性的 - 描述对象持有的数据以及人们期望在这些对象上支持的操作。
  • 哦,我明白了。我将这些读作== 符号,在这种情况下,如果您声明了上面的变量并使用标准文字语法,那么所有行都可以是常规代码。如果有某种方法可以让 C++ 接受字符串文字旁边的整数文字(我不知道,在宏之外),并且如果我的误读实际上是你写的,那么它可能是真正的检查。这就是我问的原因。
  • @DanielH,我更新了使用潜在 C++ 代码的答案。
猜你喜欢
  • 1970-01-01
  • 2013-03-27
  • 2019-07-12
  • 1970-01-01
  • 2010-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
相关资源
最近更新 更多