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

C++ 为什么使用元组而不是对象?,c++,tuples,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 product

我工作的代码库有一个名为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();
}
List productIds=blah();
//剪断许多行和方法调用
无效剂量测定(对id){
整数productId=id.first();
整数数量=id.second();
}
而不是

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

List<ProductsOrderded> productsOrdered = blah();
类产品排序{
int-productId;
整数数量有序;
//存取器方法等
}
List productsOrdered=blah();
在代码库中,该对的许多其他用法也同样难闻


我在谷歌上搜索了元组,它们似乎经常被误解或以可疑的方式使用。是否有令人信服的理由支持或反对使用它们?我很欣赏不想创建巨大的类层次结构,但是如果不使用元组,类层次结构是否会在现实的代码库中爆炸?

首先,元组是快速而简单的:不是每次你想把两个东西放在一起时都要编写一个类,而是有一个模板为你做这件事

其次,它们是通用的。例如,在C++中,STD::MAP使用STD::GAND键和值对。因此,可以使用任何一对,而不必为两种类型的每一个置换生成某种带有访问器方法的包装类

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


平心而论,您粘贴的代码对一对代码的使用是不好的。

这只是原型质量的代码,很可能是拼凑在一起的,从未被重构过。不解决它只是懒惰


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

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


有时候,你真的只需要把事情配对,创造一个真实的、对上帝诚实的班级,这是过分的。另一方面,当你真的应该使用一个类的时候,使用元组和使用相反的概念一样糟糕。

一个明显的例子是坐标对(或三元组)。标签是不相关的;使用X和Y(和Z)只是一种惯例。将它们统一起来可以清楚地表明,它们可以以同样的方式处理。

无论如何,OP中的代码是一团乱麻,不是因为它使用元组,而是因为元组中的值类型太弱。比较以下各项:

List<Pair<Integer, Integer>> products_weak = blah1();
List<Pair<Product, Integer>> products_strong = blah2();
列出产品_弱=blah1();
列出产品_strong=blah2();
如果我的开发团队传递ID而不是类实例,我也会感到不安,因为整数可以表示任何东西


话虽如此,元组在正确使用时非常有用:

  • 元组用于将临时值分组在一起。它们当然比创建过多的包装类要好
  • 当需要从函数返回多个值时,out/ref参数的有用替代方法
然而,C#中的元组让我大吃一惊。许多语言,如OCaml、Python、Haskell、F#等,都有一种特殊、简洁的语法来定义元组。例如,在F#中,定义构造函数如下:

val of_list : ('key * 'a) list -> Map<'key,'a>
val of_list:('key*'a)list->Map
我可以使用以下方法创建地图实例:

(* 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
(*val值:(int*string)列表*)
设值=
[1,“美国”;
2、“加拿大”;
3、“英国”;
4.“澳大利亚”;
5,“斯洛文尼亚”]
(*val dict:Map*)
设dict=Map.of_列表值
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);
var值=新元组[]{
新元组(1,“US”),
新元组(2,“加拿大”),
新元组(3,“英国”),
新元组(4,“澳大利亚”),
新元组(5,“斯洛文尼亚”)
}
var dict=新字典(值);

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

已经提到了很多东西,但我认为还应该提到,有一些编程风格不同于OOP,对于这些元组来说非常有用


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

代码示例有一些不同的味道:

  • 重塑车轮
框架中已经有可用的元组;
KeyValuePair
结构。
Dictionary
类使用它来存储对,但您可以在任何适合的地方使用它。(并不是说它适合这种情况……)

  • 制作方轮
如果您有一个对列表,最好使用
KeyValuePair
结构,而不要使用具有相同用途的类,因为这样会减少内存分配

  • 隐瞒意图

带有属性的类清楚地显示了值的含义,而
类不会告诉您值表示什么(只是它们可能以某种方式相关)。要使代码合理地用这样的列表来解释,您必须给列表一个非常描述性的名称,比如
productIdAndQuantityPairs

它是代码重用。与其编写另一个与我们创建的最后5个元组类结构完全相同的类,不如让。。。一个元组类,并在需要元组时使用它

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

如果您正在进行排序
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}
val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)