为什么使用元组而不是对象?

时间:2009-03-23 21:08:21

标签: c++ tuples

我工作的代码库有一个名为Pair的对象,其中A和B是Pair中第一个和第二个值的类型。我发现这个对象是冒犯性的,因为它被用来代替具有明确命名成员的对象。所以我发现了这个:

List<Pair<Integer, Integer>> productIds = blah();
// snip many lines and method calls

void doSomething(Pair<Integer, Integer> id) {
  Integer productId = id.first();
  Integer quantity = id.second();
}

而不是

class ProductsOrdered {
  int productId;
  int quantityOrdered;
  // accessor methods, etc
}

List<ProductsOrderded> productsOrdered = blah();

代码库中对的许多其他用途同样难闻。

我用谷歌搜索了元组,它们似乎经常被误解或以可疑的方式使用。对他们的使用是否存在令人信服的论据?我可以理解不想创建庞大的类层次结构,但是如果没有使用元组,那么类层次结构会爆炸吗?

10 个答案:

答案 0 :(得分:36)

首先,一个元组是快速而简单的:每次你想把两件事放在一起时,不是写一个类,而是有一个模板可以帮你完成。

其次,它们是通用的。例如,在C ++中,std :: map使用std :: pair of key和value。因此可以使用ANY对,而不必为两种类型的每个排列使用访问器方法创建某种包装类。

最后,它们对于返回多个值非常有用。实际上没有理由专门为函数的多个返回值创建一个类,如果它们不相关,则不应将它们视为一个对象。

公平地说,你粘贴的代码很难使用。

答案 1 :(得分:16)

在Python中一直使用元组,它们被集成到语言中并且非常有用(它们允许为初学者提供多个返回值)。

有时,你真的只需要配对并创造一个真实,诚实的上帝,上课是矫枉过正的。另一方面,当你真的应该使用一个类时使用元组就像反向一样。

答案 2 :(得分:10)

代码示例有几个不同的气味:

  • 重新发明轮子

框架中已有一个元组; KeyValuePair结构。 Dictionary类使用它来存储对,但您可以在任何适合的位置使用它。 (并不是说它适合这种情况......)

  • 制作方形轮

如果你有一个对列表,那么使用KeyValuePair结构比使用相同目的的类更好,因为它会减少内存分配。

  • 隐藏意图

具有属性的类清楚地显示了值的含义,而Pair<int,int>类没有告诉您有关值的含义(只是它们可能以某种方式相关)。为了使代码合理地自我解释,你必须给列表一个非常具有说服力的名称,如productIdAndQuantityPairs ...

答案 3 :(得分:8)

对于它的价值,OP中的代码是一团糟,不是因为它使用元组,而是因为元组中的值太弱了。比较以下内容:

List<Pair<Integer, Integer>> products_weak = blah1();
List<Pair<Product, Integer>> products_strong = blah2();

我生气太多,如果我的开发团队进行了绕过标识,而不是围绕类实例,因为一个整数能代表什么


话虽如此,当您正确使用它们时,元组非常有用:

  • 存在元组以将ad hoc值组合在一起。它们肯定比创建过多的包装类更好。
  • 当您需要从函数返回多个值时,有用的替代out / ref参数。

然而,C#中的元组使我的眼睛流水。许多语言,如OCaml,Python,Haskell,F#等,都有一个特殊的,简洁的语法来定义元组。例如,在F#中,Map module定义了一个构造函数,如下所示:

val of_list : ('key * 'a) list -> Map<'key,'a>

我可以使用以下方法创建地图实例:

(* val values : (int * string) list *)
let values = 
    [1, "US";
     2, "Canada";
     3, "UK";
     4, "Australia";
     5, "Slovenia"]

(* val dict : Map<int, string> *)
let dict = Map.of_list values

C#中的等价代码是荒谬的:

var values = new Tuple<int, string>[] {
     new Tuple<int, string>(1, "US"),
     new Tuple<int, string>(2, "Canada"),
     new Tuple<int, string>(3, "UK"),
     new Tuple<int, string>(4, "Australia"),
     new Tuple<int, string>(5, "Slovenia")
     }

 var dict = new Dictionary<int, string>(values);

我不相信元组原则有什么问题,但C#的语法太麻烦,无法充分利用它们。

答案 4 :(得分:4)

这是代码重用。而不是编写与我们制作的最后5个类似于类的类完全相同的结构的另一个类,你制作一个元组类,并在需要元组时使用它。

如果该类的唯一意义是“存储一对值”,那么我会说使用元组是一个明显的想法。如果你开始实现多个相同的类只是为了让你可以重命名这两个成员,我会说这是代码味道(就像我讨厌这个术语一样)。

答案 5 :(得分:2)

这只是原型质量的代码,很可能被粉碎在一起并且从未被重构过。不修复它只是懒惰。

元组的真正用途是用于通用功能,它实际上并不关心组件是什么,而只是在元组级别运行。

答案 6 :(得分:2)

Scala具有元组值类型,从2元组(Pairs)到具有20多个元素的元组。见First Steps to Scala(第9步):

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

如果您需要将某些相对 ad hoc 目的的值捆绑在一起,则元组非常有用。例如,如果您有一个需要返回两个相当不相关的对象的函数,而不是创建一个新类来保存这两个对象,则从函数返回一个Pair。

我完全赞同其他海报可以滥用元组。如果元组具有对应用程序很重要的任何语义,则应使用适当的类。

答案 7 :(得分:1)

明显的例子是坐标对(或三重)。标签无关紧要;使用X和Y(和Z)只是一种惯例。使它们变得均匀可以清楚地表明它们可以用同样的方式处理。

答案 8 :(得分:1)

已经提到了很多东西,但我认为还应该提一下,有些编程风格与OOP不同,而且这些元组非常有用。

像Haskell这样的函数式编程语言根本就没有类。

答案 9 :(得分:0)

如果你正在做一个Schwartzian transform按特定键排序(重复计算代价很高),或类似的东西,那么一个类似乎有点矫枉过正:

val transformed = data map {x => (x.expensiveOperation, x)}
val sortedTransformed = transformed sort {(x, y) => x._1 < y._1}
val sorted = sortedTransformed map {case (_, x) => x}

拥有class DataAndKey或其他任何东西在这里看起来有点多余。

你的例子不是一个元组的好例子,但我同意。