【问题标题】:What are good use-cases for tuples in C++11?C++11 中的元组有哪些好的用例?
【发布时间】:2012-05-02 19:31:38
【问题描述】:

在 C++11 中使用元组有哪些好的用例?例如,我有一个定义局部结构的函数,如下所示:

template<typename T, typename CmpF, typename LessF>
void mwquicksort(T *pT, int nitem, const int M, CmpF cmp, LessF less)
{
  struct SI
  {
    int l, r, w;
    SI() {}
    SI(int _l, int _r, int _w) : l(_l), r(_r), w(_w) {}
  } stack[40];

  // etc

我正在考虑用 std::tuple&lt;int,int,int&gt; 替换 SI 结构,这是一个更短的声明,其中包含已经预定义的方便的构造函数和运算符,但有以下缺点:

  • 元组元素隐藏在模糊的、实现定义的结构中。即使 Visual Studio 很好地解释和显示了它们的内容,我仍然不能放置依赖于元组元素值的条件断点。
  • 访问单个元组字段 (get&lt;0&gt;(some_tuple)) 比访问结构元素 (s.l) 详细得多。
  • 按名称访问字段比按数字索引提供更多信息(而且更短!)。

tie 函数在某种程度上解决了最后两点。考虑到这些缺点,什么是元组的好用例?

更新 结果发现 VS2010 SP1 调试器无法显示以下数组 std::tuple&lt;int, int, int&gt; stack[40] 的内容,但在使用 struct 编码时可以正常工作。所以这个决定基本上是不费吹灰之力的:如果你必须检查它的值,使用 struct [esp.对 GDB 等调试器很重要]。

【问题讨论】:

  • 索引问题可以通过正确定义的常量/枚举来解决。
  • 你写了一个排序函数,接受T*, size? lolwot,你为什么会做这样的事情。
  • @zvrba :他不是在拖钓(或者他可能是,但是),他是对的。
  • "仅供参考,主程序中的底层数据结构是一个向量(我不是受虐狂,所以我不想手动摆弄分配),指针来自&amp;vec[0]." 所以传递 std::vector&lt;T&gt;&amp; 而不是 T* 并使 API 更合理。 (顺便说一句,对你希望帮助你的人咄咄逼人是有点愚蠢的。)
  • @zvrba:只需要两个通用迭代器。通过v.begin()v.end(),或std::begin(arr)std::end(arr)。完成。

标签: c++ c++11 tuples


【解决方案1】:

从一个函数返回多个值是一种简单的方法;

std::tuple<int,int> fun();

结果值可以优雅地使用如下:

int a;
int b;
std::tie(a,b)=fun();

【讨论】:

  • 你不能使用auto。您不能使用复制构造函数(因此不能使用 RVO)。如果可以写std::tie(auto a, auto b)=fun();,那将是有意义的。你可以写auto tuple = func();,然后使用std::get&lt;0&gt;(tuple);std::get&lt;1&gt;(tuple);,但这太糟糕了。
  • @anton_rh 结构化绑定已经出现在 C++17 中并解决了您的问题。
【解决方案2】:

嗯,恕我直言,最重要的部分是通用代码。编写适用于各种结构的泛型代码比编写适用于元组的泛型要困难得多。例如,您自己提到的 std::tie 函数几乎不可能用于结构。

这允许你做这样的事情:

  • 存储延迟执行的函数参数(例如this question
  • 返回多个参数而无需使用std::tie 进行繁琐(解)打包
  • 组合(非等类型)数据集(例如来自并行执行),可以像std::tuple_cat 一样简单地完成。

问题是,它并不止于这些用途,人们可以扩展此列表并基于元组编写通用功能,这对于结构来说更难。谁知道呢,也许明天有人会发现用于序列化的绝妙用途。

【讨论】:

  • 标记为答案,因为你让我知道tuple_cat,它没有在 VS2010 的 MSDN 中列出。但是,对于某些元组xy,它们的类型不容易从函数参数中推导出来,如何声明返回tuple_cat(x,y) 的函数的返回值?
  • 我对您的第一个元组用例很感兴趣,使用std::bind 存储函数参数不是比使用元组更容易吗?这是在您链接到的问题上提出的,但答案只是其他人正在给他们元组。
【解决方案3】:

我认为tuples 的大部分用途来自std::tie

bool MyStruct::operator<(MyStruct const &o) const
{
    return std::tie(a, b, c) < std::tie(o.a, o.b, o.c);
}

以及此处答案中的许多其他示例。然而,我发现这个示例是最常用的,因为它比以前在 C++03 中的方式节省了很多精力。

【讨论】:

  • 是的,我今天才发现并使用了它。我使用了一种稍微复杂/通用的方法,我可能会充实到另一个答案中。
【解决方案4】:

你用过std::pair吗?您使用std::tuple 的许多地方都相似,但不限于恰好两个值。

您为元组列出的缺点也适用于 std::pair,有时您想要一个比firstsecond 更具有表现力的类型,其成员名称更好,但有时您不需要。这同样适用于元组。

【讨论】:

    【解决方案5】:

    我认为除了一些通用库功能的实现细节之外,元组没有什么用处。

    (可能)节省打字并不能抵消结果代码的自文档属性的损失。

    用元组代替结构,只为字段取一个有意义的名称,用“数字”替换字段名称(就像 std::pair 的错误概念一样)。

    使用元组返回多个值的自文档化比其他方法要少得多——返回命名类型或使用命名引用。如果没有这种自我记录,如果返回值可以相互转换,则很容易混淆返回值的顺序。

    【讨论】:

    • 按顺序传递参数并不比使用元组返回多个值好多少。您仍然可以混淆参数的顺序。但至少每个参数都有自己的名称。元组的值没有。
    【解决方案6】:

    真正的用例是你有不可命名元素的情况——可变参数模板和 lambda 函数。在这两种情况下,您都可以拥有具有未知类型的未命名元素,因此存储它们的唯一方法是具有未命名元素的结构:std::tuple。在所有其他情况下,您拥有已知 # 个具有已知类型的可命名元素,因此可以使用普通结构,这是 99% 的最佳答案。

    例如,您不应该使用 std::tuple 从具有固定数量的通用输入的普通函数或模板中获得“多次返回”。为此使用真实的结构。真实对象比 std::tuple 千篇一律更“通用”,因为您可以为真实对象提供任何接口。它还将在公共库中为您提供更多的类型安全性和灵活性。

    只需比较这两个类成员函数:

    std::tuple<double, double, double>  GetLocation() const; // x, y, z
    
    GeoCoordinate  GetLocation() const;
    

    使用真正的“地理坐标”对象,我可以提供一个运算符 bool(),如果父对象没有位置,则返回 false。通过其 API,用户可以获得 x、y、z 位置。但重要的是——如果我决定通过在 6 个月内添加时间字段来制作 GeoCoordinate 4D,则当前用户的代码不会中断。我不能用 std::tuple 版本做到这一点。

    【讨论】:

      【解决方案7】:

      与其他使用元组的编程语言互操作,并返回多个值而无需调用者理解任何额外的类型。这是我想到的前两个。

      【讨论】:

      • @zvrba:那是“编译”时互操作,运行时互操作也是可能的。此外,另一种语言可以使用 C++ 接口。我敢打赌像boost.python 这样的东西可以从中受益。
      • @zvrba:C++ 与 .NET、Python、Lua、Ruby、Perl、Fortran 等互操作。此处的互操作意味着能够链接和调用函数……如果你要编写Boost Python、Luabind、Rcpp 等胶水库,很高兴将元组等功能作为语言词汇表的一部分提供,以使互操作看起来尽可能无缝。
      • @JohnZwinck “互操作”对我来说意味着在不编写粘合层的情况下调用其他语言的函数。所以这包括我忘记的 C、C++ 和 Fortran。但我同意拥有允许更紧密地反映“其他语言”API 的元组是件好事。
      【解决方案8】:

      我无法评论 mirk 的答案,所以我必须给出一个单独的答案:

      我认为元组被添加到标准中也是为了允许函数式编程。举个例子,虽然像

      这样的代码
      void my_func(const MyClass& input, MyClass& output1, MyClass& output2, MyClass& output3)
      {
         // whatever
      }
      

      在传统的 C++ 中无处不在,因为它是让函数返回多个对象的唯一方法,这对于函数式编程来说是一种可恶的做法。现在你可以写了

      tuple<MyClass, MyClass, MyClass> my_func(const MyClass& input)
      {
         // whatever
         return tuple<MyClass, MyClass, MyClass>(output1, output2, output3);
      }
      

      因此有机会避免副作用和可变性,允许流水线操作,同时保持函数的语义强度。

      【讨论】:

      • 标准反对意见:struct MyFuncReturn { MyClass output1, output2; }; MyFuncReturn my_func(MyClass const&amp; input); 不是更好吗?如果我们在谈论“语义强度”,那么专门构建的struct 比任何tuple 都要多得多。当然,它与函数参数的现有含义相似——只有顺序很重要,而不是名称——这对你来说可能就足够了。然而,同样的论点——语义意义——是为什么人们不断地试图找到一种方法来使命名论点在这样的语言中工作,而不会引入完全的破坏/脆弱性。他们会嘲笑tuple
      【解决方案9】:

      F.21:要返回多个“out”值,最好返回一个结构或元组

      更喜欢使用命名结构,其中返回值有语义。否则,无名元组在泛型代码中很有用。

      例如,如果返回的值是来自输入流的值和错误代码,则这些值不会自负。它们的相关性不足以证明有一个专门的结构来容纳两者。不同的是,x 和 y 对宁愿有一个像 Point 这样的结构。

      source I reference 由 Herb Sutter 的 Bjarne Stroustrup 维护,所以我认为有点值得信赖。

      【讨论】:

        猜你喜欢
        • 2011-05-04
        • 2014-09-30
        • 1970-01-01
        • 2014-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-11
        • 1970-01-01
        相关资源
        最近更新 更多