【问题标题】:Encapsulating Fundamental Data Types Into Classes将基本数据类型封装到类中
【发布时间】:2012-01-13 02:33:36
【问题描述】:

不讨论这是否是一个好主意,如果他们将内置的 C++ 数据类型封装到自己的类中,将会面临什么样的缺点(性能或其他方面)。例如,与 Java 和 C# 类似,int 数据类型将有自己的名为 Int 的类,并使用内联运算符对其进行重载。与 Single、Double、Long 等相同。

【问题讨论】:

  • 我可以问相反的问题吗?有什么优势?
  • 与所有此类“透明”包装器一样,operator& 的语义将是双输局面。
  • 一个优点是您可以让它们继承基类。 Int32:整数:数字:对象。当您不想重载具有多种类型的 Math 类时,这可能很有用,而只需 Integer 或 Real 就足够了。这也允许您将新类型添加到类型族中,例如添加 Int128 不会影响数学类。
  • @Dave:那么IntegerNumber 允许哪些操作?在我看来,或多或少有两种选择:要么根本没有(这将使其完全一文不值),要么所有操作都实现为虚拟方法(这将对性能产生严重影响)。此外,这不是我们有模板的用途吗?
  • 你确定让他们继承基类是一个优势吗?无论如何,您可以使用模板编写类型通用代码。数学课有什么用? “数学对象”的含义是什么?如果您尝试使用 C++ 制作 Java 或 C#,请不要这样做。我建议你改用learn C++

标签: c++ performance optimization compiler-construction


【解决方案1】:

没有任何好处。您无法实现相同的行为或性能。


缺点:

  • 性能永远不会比内置类型。您将获得的最好的结果是所有内容都内联到内置类型代码的类,但这有什么意义?
  • 更多代码无用
  • 虚拟调用有开销
  • 无法实现相同的行为,例如使用运算符(主要是铸造)
  • 非平凡的构造函数(在 C++11 中不一定)
  • 模板参数等 C++ 功能不支持
  • 令人困惑。没有人这样做。

当您不想重载具有多种类型的 Math 类时,这可能很有用,而只需 Integer 或 Real 就足够了。

Java 卡在你的脑海里。

C++ 采用模板方式:

template<typename A_type, typename B_type>
auto math_operation(A_type a, B_type b) -> decltype(a + b * 2) {
  return a + b * 2;
}

现在您有了一个可以在任何支持正确运算符的类型上工作的函数。这适用于内置类型和 Int128 等类。

【讨论】:

  • “Java 卡在你的脑袋里了。”-不知道有没有办法把它弄出来? :)
  • 您能详细说明您无法模仿的行为吗?
  • 其实它的 C# 卡在了我的脑海里,而 Java 卡在了微软的头上:-P
  • @R.MartinhoFernandes 我添加了一些。如果您知道其他任何事情,请说出来。
  • @Jesse 垃圾收集器一旦意识到它是多么无用,就会清理干净。
【解决方案2】:

清晰度会受到影响,大部分情况就是这样。

如果您有一个智能编译器,那么一个简单地包装 int 并且不更改任何操作的类很可能会完全内联。如果您不将构造函数定义为显式,您甚至可以为void f(OurVerySpecialInt i) 编写f(15)。但是,如果有的话,您将很难将非常特殊的 Int 传递给现有函数。

但是,如果您指的是类层次结构而不仅仅是一个类,情况就大不相同了。你想让Numeric 成为一个抽象基类,而IntDouble 派生自它吗?在这种情况下,请重新考虑。您不仅最终可能会得到显着变慢的代码,而且没有真正的方法可以同时使这个通用和理智。

让我们考虑一个重载operator+ 的类Numeric。运算符要么是非成员(应该是),然后它不能是虚拟的:因此,它必须调用Numeric 的虚拟成员函数。但是哪一个? Double() + Double() 是否返回 DoubleDouble() + Int() 呢? Double() + Rational() 呢?在前两种情况下,您可以说“Double,当然,因为有更多可能的值”,但在最后一种情况下不起作用:如果 Double 是 64 位的,而 Rational 是两个 32 位整数的商,您将在每个整数中都有无法用另一个表示的值(例如正无穷大和 0.3)。

除此之外,您的函数可以承诺的很少。 Int32 i = 250; i += 250; 之后的 i 值是多少?我假设它是500Int8 = 250; Int8 += 250; 呢? Numeric* p = new Int8(250); *p += 250; 呢。你不能神奇地让*p 变大,所以要么你犯错误,要么溢出;基本上,如果p 是一些Numeric*,你不知道*p += 50000; 会做什么:按预期工作或溢出/错误,你也不知道你在做@ 时是否会丢失精度987654345@.

如果您通过使条件更严格来修复这些错误,您最终会得到某种不需要任何继承的RationalBigInt 类;所有行为都被如此严格地指定(因为它应该与数学实体一样),从它派生不会真正让你改变任何东西。

如果这还不够,请注意,如果您为 Numeric 类提供任何虚拟方法,那么您的所有派生类都将具有一个 vtable(在常见实现下)。这意味着你的类的每个实例都需要比平时多一点的空间,并且所有数值都是正常大小的两倍可能会严重影响性能,具体取决于你在做什么。

【讨论】: