44

我试图让我的头脑围绕元组(感谢@litb),他们使用的常见建议是返回> 1值的函数。

这是我通常会使用 struct 的东西,在这种情况下我无法理解元组的优势 - 对于最终懒惰的人来说,这似乎是一种容易出错的方法。

借用一个例子,我会用这个

struct divide_result {
    int quotient;
    int remainder;
};

使用元组,您将拥有

typedef boost::tuple<int, int> divide_result;

但是,如果不阅读您正在调用的函数的代码(或注释,如果您愚蠢到相信它们),您将不知道哪个 int 是商,反之亦然。好像比较像...

struct divide_result {
    int results[2]; // 0 is quotient, 1 is remainder, I think
};

...这不会让我充满信心。

那么,元组与弥补歧义的结构相比有什么优势

4

9 回答 9

24

元组

我想我同意你的观点,即什么位置对应什么变量的问题会引起混淆。但我认为有两个方面。一个是调用方,另一个是被调用方

int remainder; 
int quotient;
tie(quotient, remainder) = div(10, 3);

我认为我们得到了什么非常清楚,但是如果您必须一次返回更多值,它可能会变得混乱。一旦调用者的程序员查阅了 的文档div,他就会知道什么位置是什么,并且可以编写有效的代码。根据经验,我会说不要一次返回超过 4 个值。除此之外,更喜欢结构。

输出参数

当然也可以使用输出参数:

int remainder; 
int quotient;
div(10, 3, &quotient, &remainder);

现在我认为这说明了元组如何优于输出参数。我们将 的输入div与输出混合在一起,却没有获得任何优势。更糟糕的是,我们让该代码的读者对 be 的实际返回值产生了疑问div。当输出参数有用时,有很多很好的例子。在我看来,只有在没有其他方法的情况下才应该使用它们,因为返回值已经被采用并且不能更改为元组或结构。operator>>是使用输出参数的一个很好的例子,因为返回值已经为流保留,所以你可以链接operator>>来电。如果您不使用运算符,并且上下文不是很清楚,我建议您使用指针,在调用端发出信号表明该对象实际上用作输出参数,以及适当的注释。

返回一个结构

第三种选择是使用结构:

div_result d = div(10, 3);

我认为这绝对赢得了清晰奖。但是请注意,您仍然必须访问该结构中的结果,并且结果不会“裸露”在桌子上,就像输出参数和与tie.

我认为这些天的一个重点是让一切尽可能通用。所以,假设你有一个可以打印元组的函数。你可以做

cout << div(10, 3);

并显示您的结果。我认为另一方面,元组显然因其多功能性而获胜。使用 div_result 执行此操作,您需要重载 operator<<,或者需要单独输出每个成员。

于 2009-01-03T21:53:37.980 回答
10

另一种选择是使用 Boost Fusion 映射(代码未经测试):

struct quotient;
struct remainder;

using boost::fusion::map;
using boost::fusion::pair;

typedef map<
    pair< quotient, int >,
    pair< remainder, int >
> div_result;

您可以相对直观地访问结果:

using boost::fusion::at_key;

res = div(x, y);
int q = at_key<quotient>(res);
int r = at_key<remainder>(res);

还有其他优点,例如遍历地图字段的能力等。有关更多信息,请参阅文档

于 2009-01-03T22:48:45.843 回答
5

使用元组,您可以使用tie,这有时非常有用:std::tr1::tie (quotient, remainder) = do_division ();. 这对于结构来说并不容易。其次,在使用模板代码时,有时依赖对比为结构类型添加另一个 typedef 更容易。

如果类型不同,那么一对/元组实际上并不比结构差。例如pair<int, bool> readFromFile(),int 是读取的字节数,bool 是 eof 是否被命中。在这种情况下添加一个结构对我来说似乎有点矫枉过正,特别是因为这里没有歧义。

于 2009-01-03T21:05:19.093 回答
4

元组在 ML 或 Haskell 等语言中非常有用。

在 C++ 中,它们的语法降低了它们的优雅性,但在以下情况下很有用:

  • 您有一个必须返回多个参数的函数,但结果对于调用者和被调用者来说是“本地的”;你不想为此定义一个结构

  • 您可以使用 tie 函数来做一种非常有限的模式匹配“a la ML”,这比为相同目的使用结构更优雅。

  • 它们带有预定义的 < 运算符,可以节省时间。

于 2009-01-03T21:46:26.343 回答
3

我倾向于将元组与 typedef 结合使用,以至少部分缓解“无名元组”问题。例如,如果我有一个网格结构,那么:

//row is element 0 column is element 1
typedef boost::tuple<int,int> grid_index;

然后我将命名类型用作:

grid_index find(const grid& g, int value);

这是一个有点做作的例子,但我认为大多数时候它在可读性、明确性和易用性之间达到了一个愉快的平衡。

或者在您的示例中:

//quotient is element 0 remainder is element 1
typedef boost:tuple<int,int> div_result;
div_result div(int dividend,int divisor);
于 2009-01-03T22:27:15.090 回答
3

结构体所没有的元组的一个特性是它们的初始化。考虑以下内容:

struct A
{
  int a;
  int b;
};

除非您编写make_tuple等效或构造函数,否则要将此结构用作输入参数,您首先必须创建一个临时对象:

void foo (A const & a)
{
  // ...
}

void bar ()
{
   A dummy = { 1, 2 };
   foo (dummy);
}

还不错,但是,以维护出于任何原因向我们的结构添加新成员的情况为例:

struct A
{
  int a;
  int b;
  int c;
};

聚合初始化的规则实际上意味着我们的代码将继续编译而不会发生变化。因此,我们必须搜索此结构的所有用法并更新它们,而无需编译器的任何帮助。

将此与元组进行对比:

typedef boost::tuple<int, int, int> Tuple;
enum {
  A
  , B
  , C
};

void foo (Tuple const & p) {
}

void bar ()
{
  foo (boost::make_tuple (1, 2));  // Compile error
}

编译器无法使用 的结果初始化“Tuple” make_tuple,因此会生成允许您为第三个参数指定正确值的错误。

最后,元组的另一个优点是它们允许您编写迭代每个值的代码。这根本不可能使用结构。

void incrementValues (boost::tuples::null_type) {}

template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
   // ...
   ++tuple.get_head ();
   incrementValues (tuple.get_tail ());
}
于 2009-06-25T10:04:57.953 回答
2

防止你的代码被许多结构定义乱七八糟。当您只记录元组中的每个元素是什么而不是编写自己的结构/让人们查找结构定义时,编写代码的人以及使用它的其他人更容易。

于 2009-01-03T21:08:22.410 回答
2

元组将更容易编写 - 无需为每个返回某些内容的函数创建一个新结构。关于什么去哪里的文档将转到功能文档,无论如何都需要。要使用该函数,无论如何都需要阅读函数文档,并且将在那里解释元组。

于 2009-01-03T21:19:15.023 回答
-1

我同意你 100% 罗迪。

要从一个方法返回多个值,除了元组之外,您还有几个选项,哪个最好取决于您的情况:

  1. 创建一个新结构。当您返回的多个值是相关的时,这很好,并且适合创建新的抽象。例如,我认为“divide_result”是一个很好的通用抽象,传递这个实体让你的代码比传递一个无名元组更清晰。然后,您可以创建对该新类型进行操作的方法,将其转换为其他数字类型等。

  2. 使用“输出”参数。通过引用传递多个参数,并通过分配给每个 out 参数返回多个值。当您的方法返回几个不相关的信息时,这是合适的。在这种情况下创建一个新结构将是矫枉过正的,并且使用 Out 参数可以强调这一点,而且每个项目都会获得应有的名称。

元组是邪恶的。

于 2009-01-03T21:24:45.933 回答