为什么在C ++中使用元组并不常见?


124

为什么似乎没有人在C ++中使用元组,无论是Boost Tuple库还是TR1的标准库?我读了很多C ++代码,很少看到元组的用法,但是我经常看到很多地方元组可以解决许多问题(通常从函数返回多个值)。

元组可以让您做各种很酷的事情,例如:

tie(a,b) = make_tuple(b,a); //swap a and b

那肯定比这更好:

temp=a;
a=b;
b=temp;

当然,您始终可以这样做:

swap(a,b);

但是,如果要轮换三个值怎么办?您可以使用元组来执行此操作:

tie(a,b,c) = make_tuple(b,c,a);

元组还使从函数返回多个变量变得更加容易,这可能比交换值更常见。使用引用返回值当然不是很好。

我没有想到的元组有什么大的缺点吗?如果没有,为什么很少使用它们?他们慢吗?还是仅仅是人们不习惯他们?使用元组是个好主意吗?


17
为聪明的元组交换技巧+1 :)
kizzx2

10
a = a ^ b; b = a ^ b; a = a ^ b;
Gerardo Marset 2011年

3
IMO元组在弱键入语言或本机结构的语言中很方便。例如,在Python或PHP中,它们使生活变得更轻松,而在C ++中,太多的键入(无法从模板构造它)和太多的好处。
doc

4
对OP的评论:我认为当前接受的答案已经过时,以致于事实是错误的。您可能需要重新考虑接受答案的选择。
ulidtko 2014年

8
@GerardoMarset你是认真的吗?
thesaint 2015年

Answers:


43

因为它不是标准的。任何非标准的障碍都更高。Boost的某些部分之所以流行,是因为程序员大声疾呼。(hash_map让我记忆犹新)。但是,虽然元组很方便,但并不是让人为之烦恼的压倒性的胜利。


1
人们似乎疯狂使用Boost的其他部分。尽管可以肯定,哈希映射通常比元组有用得多。
Zifre

我不知道您所看到的细节,但我猜人们正在疯狂使用的部件是他们真正想要的功能。因此(再次猜测)哈希图,计数的指针等的流行。元组很方便,但是它并不是一个可以填补空缺的东西。回报并不明显。您需要多久精确旋转N个对象?(与需要旋转任意长向量相反)。人们习惯于通过引用传递返回值,或者返回小型类或结构。
艾伦·德·史密特

20

124

一个愤世嫉俗的答案是,许多人使用C ++进行编程,但不了解和/或使用更高级别的功能。有时是因为不允许这样做,但是许多人根本不尝试(甚至不理解)。

作为一个非增强示例:有多少人使用中提供的功能<algorithm>

换句话说,许多C ++程序员只是使用C ++编译器的C程序员,也许std::vector还有std::list。这就是为什么使用boost::tuple不常见的原因之一。


18
对我来说是-1,因为C ++程序员并不像这个答案那样愚蠢。
2014年

5
@Mehrdad在阅读了大量的C ++代码后,无论是商业的还是非商业的,都阅读了大量的C ++材料,我认为可以肯定地说,很大一部分“ C ++”开发人员只是无法获得纯正的C开发人员。 C编译器。例如,大多数材料中几乎完全缺少模板(我已经学到了很多东西)。奇怪的宏攻击很常见,名称空间的使用严重不足。
2014年

5
废话答案。如果确实需要他们,他们将赶上他们。它们不是必需的。因此它们不被使用。说不使用它们是因为它们不容易理解是不好的。
Michael Chourdakis 2014年

9
@Michael Nonsense评论。没有什么是需要在编程一旦你掌握了一门语言的图灵完整的子集。缺乏使用肯定并不意味着每个人都了解更高级别的C ++构造并选择不使用它们。
Trey Jackson

4
我从来没有需要可变参数模板元编程之外的std :: tuple。在生活中,我没有悲伤的表情坐下,思考着“如果我只有那些元组”。实际上,当我查看元组时,我认为“理智的人会需要什么呢(我不会认为自己理智)”。元编程之外的人似乎将它们用作“匿名结构”,这很丑陋,表明他们的头脑中严重缺乏代码质量和可维护性。
thesaint 2015年

23

C ++元组语法可能比大多数人想要的更为冗长。

考虑:

typedef boost::tuple<MyClass1,MyClass2,MyClass3> MyTuple;

因此,如果您想广泛使用元组,那么要么在各处获得元组typedef,要么在各处获得烦人的长类型名。我喜欢元组。我在必要时使用它们。但这通常仅限于两种情况,例如N元素索引或使用多图绑定范围迭代器对时。而且通常范围非常有限。

与Haskell或Python之类的东西相比,它们看起来都非常丑陋。当C ++ 0x到达此处,我们得到'auto'关键字元组时,它们看起来将变得更具吸引力。

元组的有用性与声明,打包和解压缩它们所需的击键数量成反比。


大多数人会“使用命名空间提升”;不必键入boost ::。我认为键入元组不是什么大问题。话虽如此,我认为你有一点。自动可能会使更多的人开始使用元组。
Zifre

2
@Zifre:问题在于您不应该在头文件中执行“使用名称空间X”,因为它会导致名称空间污染并破坏名称空间。
福斯先生09年

1
啊,是的,我忘了标题。但是在程序代码内部,您无需担心。并且一旦有了C ++ 0x,我们就可以使用auto了,它应该消除很多类型的输入。
Zifre

19
只有我吗?我不认为他指的是7个字符“ boost ::”,而是其他33个字符。这是很多类名输入的麻烦,特别是如果这些类名也受名称空间限制。以boost :: tuple <std :: string,std :: set <std :: string>,std :: vector <My :: Scoped :: LongishTypeName>>为例。
食人魔赞美诗

10

对我来说,这是一种习惯,放下手来:元组不会为我解决任何新问题,只有几个我已经可以应付。以旧的方式交换价值仍然很容易-而且,更重要的是,我并不真正考虑如何交换“更好”。就这样就足够了。

就个人而言,我认为元组不是返回多个值的理想解决方案-听起来像是structs 的工作。


4
“我真的不考虑如何交换“更好”。”-当我编写代码时,我会编写错误。降低代码复杂度可减少我编写的错误的数量。我讨厌一次又一次地创建相同的错误。是的,我确实在考虑如何更好地<strike> swap </>代码。更少的活动部件(LOC,临时变量,错误键入的标识符),更易读的代码;好代码。
sehe 2011年

同意。包装在自动指针或智能指针中的类是类型保存的。我曾经使用过元组,但是后来使用类重写了代码。retValue.state比retValue.get <0>()更清晰。
Valentin Heinitz 2014年

1
@sehe:编写更好的,更具可读性的代码也是我的目标。添加更多类型的语法会带来成本,而且我认为“更好的交换”不会为您阅读的每一行代码考虑更多类型的语法带来精神上的负担。
ojrac

8

但是,如果要轮换三个值怎么办?

swap(a,b);
swap(b,c);  // I knew those permutation theory lectures would come in handy.

好的,所以有了4个etc值,最终n元组的代码少于n-1个交换。使用默认交换时,它会执行6个分配,而不是您自己实现一个三周期模板时分配的4个分配,尽管我希望编译器会为简单类型解决该分配。

您可以提出交换不方便或不合适的方案,例如:

tie(a,b,c) = make_tuple(b*c,a*c,a*b);

打开包装有点尴尬。

但是,要指出的是,有一些已知的方法可以处理元组最适合的最常见情况,因此没有紧迫性来处理元组。如果没有别的,我不确定:

tie(a,b,c) = make_tuple(b,c,a);

不会进行6次复制,因此完全不适用于某些类型(最明显的是集合)。可以这样说服我,说服元组是“大型”类型的一个好主意,:-)

对于返回多个值,如果值是不兼容的类型,则元组是完美的,但是如果调用者有可能以错误的顺序获取它们,则有些人不喜欢它们。有些人根本不喜欢多个返回值,也不想通过简化返回值来鼓励使用它们。有些人只喜欢使用命名结构来输入和输出参数,可能无法用棒球棒说服使用元组。没有占味。


1
您绝对不希望将向量与元组交换。我认为使用元组交换三个元素肯定比使用两次交换更清楚。对于多个返回值,out参数是有害的,结构是额外的类型,并且在某些情况下肯定需要多个返回值。
Zifre

知道为什么使用元组(而且我知道为什么要在场合出现时使用它们,尽管我认为从来没有)。我想知道为什么其他人即使知道了也不会使用它们。例如,因为他们不同意“毫无意义的邪恶”……
史蒂夫·杰索普

您知道我们是否可以替换为“ tie(a,b,c)= make_tuple(b,c,a);” 通过“ tie(a,b,c)= tie(b,c,a);” ?
雷克萨尔

2
领带(从技术上讲是一层)是使用非常量引用构成的元组。我找不到升压文档,该文档说明了在涉及的某些引用具有相同的referand的情况下,如何保证operator =和tie / tuple的副本构造函数的作用。但这就是您需要知道的。天真的操作符=显然可能会犯错……
史蒂夫·杰索普

1
@Steve:由于联系都是关于防止复制的(它们应该适用于不可复制的类型;请注意,LHS可能完全是其他东西),确实一切都应该出错(请考虑使用非POD类对象)。试想一下如何在不使用临时文件的情况下编写相同的逻辑。
sehe 2011年

7

正如许多人指出的那样,元组只是没有其他功能有用。

  1. 交换和旋转头仅仅是头。它们使以前从未见过的人感到困惑,并且由于几乎所有人,这些everyone头只是不良的软件工程实践。

  2. 与元组相比,使用元组返回多个值的自我记录要少得多-返回命名类型或使用命名引用。如果没有这种自我记录,那么很容易混淆返回值的顺序(如果它们可以相互转换),并且不明智。


6

并非所有人都可以使用Boost,TR1尚未广泛使用。


3
很多人使用Boost。这些人也可能使用元组。
Zifre

3
您问为什么人们不使用它们,我给了一个答案。
布赖恩·尼尔

2
对于不赞成投票的人:我碰巧在一个政治上无法使用boost的地方工作,即使到现在,我们使用的(对于嵌入式系统)编译器工具链也没有TR1 / C ++ 11支持。
Brian Neal 2012年

5

在嵌入式系统上使用C ++时,引入Boost库变得很复杂。它们彼此耦合,所以库的大小增加了。您返回数据结构或使用参数传递代替元组。在Python中返回元组时,数据结构的顺序和返回值的类型不明确。


5

您很少看到它们,因为设计良好的代码通常不需要它们-在很多情况下,使用匿名结构优于使用命名结构。由于元组真正代表的只是一个匿名结构,因此大多数情况下,大多数编码器都只使用真实的东西。

假设我们有一个函数“ f”,其中元组返回可能有意义。通常,此类功能通常非常复杂,以至于它们可能会失败。

如果“ f”可能失败,那么您需要返回状态-毕竟,您不希望调用者必须检查每个参数以检测失败。“ f”可能适合该模式:

struct ReturnInts ( int y,z; }
bool f(int x, ReturnInts& vals);

int x = 0;
ReturnInts vals;
if(!f(x, vals)) {
    ..report error..
    ..error handling/return...
}

那不是很漂亮,但是看看替代方案有多丑。请注意,我仍然需要一个状态值,但是代码不易读且不短。这可能也较慢,因为我使用元组会产生1份副本的费用。

std::tuple<int, int, bool> f(int x);
int x = 0;
std::tuple<int, int, bool> result = f(x); // or "auto result = f(x)"
if(!result.get<2>()) {
    ... report error, error handling ...
}

另一个重要的缺点隐藏在这里-通过“ ReturnInts”,我可以通过修改“ ReturnInts”而不增加“ f”的接口来添加“ f”的返回值。元组解决方案不提供该关键功能,因此对于任何库代码而言,它都是次等答案。


1
异常使该接口更加整洁。
大卫·斯通

公平地说(对于聚会而言非常晚),您可以通过设置using std::tuple;并仅tuple在代码中使用来简化可读性。
Alrekr 2015年

2
使用tuple使代码更少可读,而不是更多。如今,大多数代码中都包含大量符号-看到后就std::tuple可以清楚地看到它到底是什么。
汤姆·斯威

3

当然,元组可能是有用的,但是如前所述,在真正使用它们之前,存在一些开销和一两个障碍。

如果您的程序始终找到需要返回多个值或交换多个值的地方,那么走元组路线可能是值得的,但是有时候以经典方式进行操作会更容易。

一般而言,并不是每个人都已经安装了Boost,并且我当然不会为下载它和配置我的include目录而仅为其元组功能使用它而烦恼。我想您会发现,已经使用Boost的人比非Boost用户更容易在其程序中找到元组的使用,并且来自其他语言的移民(想到Python)更可能因缺少元组而感到不安比起在C ++中探索添加元组支持的方法。


1

由于数据存储std::tuple具有struct和阵列最差的特性;所有访问均基于第n个位置,但无法tuple使用for循环进行遍历。

因此,如果从tuple概念上讲,元素中的元素是数组,那么我将使用数组;如果从概念上讲,元素不是数组,则结构(具有命名元素)将更易于维护。(a.lastname比更具解释性std::get<1>(a))。

这使OP提到的转换成为元组唯一可行的用例。


0

我感觉很多人都使用Boost.Any和Boost.Variant(经过一些工程)而不是Boost.Tuple。


为什么要用高效的静态类型来换那些东西呢?
Zifre

Boost.Variant是完全类型安全的。
user21714

1
糟糕,是的,它是类型安全的,但是可以进行运行时类型输入。
Zifre

5
我看不到如何将Tuple替换为Any / Variant。他们做的不一样。
Mankarse 2011年

1
@Zifre我不能代表作者说话,但是我认为这里的含义是将它们与其他容器类型一起使用。
Tim Seguine 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.