我正试图绕过元组(感谢@litb),他们使用的常见建议是返回的函数> 1值。
这是我通常使用结构的东西,在这种情况下我无法理解元组的优点 - 对于最终懒惰来说,这似乎是一种容易出错的方法。
Borrowing an example,我会用这个
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
};
......这不会让我满怀信心。
那么,是什么元组优于结构的优势可以弥补模糊性?
答案 0 :(得分:24)
我认为我同意你的观点,即什么位置的问题对应于什么变量会引起混乱。但我认为有两个方面。一个是呼叫方,另一个是被叫方:
int remainder;
int quotient;
tie(quotient, remainder) = div(10, 3);
我认为我们得到的结果非常清楚,但如果你必须一次返回更多的值,它会变得混乱。一旦调用者的程序员查找了div
的文档,他就会知道什么位置是什么,并且可以编写有效的代码。根据经验,我会说不会一次返回超过4个值。对于任何事情,更喜欢结构。
当然也可以使用输出参数:
int remainder;
int quotient;
div(10, 3, "ient, &remainder);
现在我认为这说明了元组如何比输出参数更好。我们将div
的输入与其输出混合在一起,但没有获得任何优势。更糟糕的是,我们让该代码的读者对div
的实际返回值有什么疑问。当输出参数有用时,是很好的例子。在我看来,只有当你没有其他方法时才应该使用它们,因为返回值已经被采用并且不能更改为元组或结构。 operator>>
是使用输出参数的一个很好的示例,因为返回值已经为流保留,因此您可以链接operator>>
个调用。如果您不关心运算符,并且上下文不清楚,我建议您使用指针,在调用方面发信号通知该对象实际上用作输出参数,以及适当的注释。 / p>
第三种选择是使用结构:
div_result d = div(10, 3);
我认为肯定会因清晰度而获奖。但请注意,您仍然要在该结构中访问结果,并且结果不会在表上“裸露”,因为输出参数和与tie
一起使用的元组就是这种情况。
我认为现在最重要的一点是尽可能使一切尽可能通用。所以,假设你有一个可以打印出元组的函数。你可以做到
cout << div(10, 3);
显示结果。我认为,另一方面,元组明显地赢得了多才多艺的性质。使用div_result执行此操作时,需要重载运算符&lt;&lt;,或者需要单独输出每个成员。
答案 1 :(得分: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);
还有其他优点,例如迭代在地图字段上的能力等等。有关详细信息,请参阅doco。
答案 2 :(得分:5)
使用元组,您可以使用tie
,这有时非常有用:std::tr1::tie (quotient, remainder) = do_division ();
。结构不是那么容易的。其次,在使用模板代码时,有时更容易依赖对,而不是为结构类型添加另一个typedef。
如果类型不同,那么一对/元组实际上并不比结构差。例如pair<int, bool> readFromFile()
,其中int是读取的字节数,bool是eof是否被命中。在这种情况下添加一个结构对我来说似乎有些过分,特别是因为这里没有歧义。
答案 3 :(得分:4)
元组在ML或Haskell等语言中非常有用。
在C ++中,它们的语法使它们不那么优雅,但在以下情况下可能很有用:
你有一个必须返回多个参数的函数,但结果是调用者和被调用者的“本地”;你不想只为这个
你可以使用tie函数来进行非常有限的模式匹配“a la ML”,这比使用同一目的的结构更优雅。
他们带有预定义的&lt;运营商,可以节省时间。
答案 4 :(得分: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);
答案 5 :(得分: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
}
编译器无法使用make_tuple
的结果初始化“Tuple”,因此生成的错误允许您为第三个参数指定正确的值。
最后,元组的另一个优点是它们允许您编写迭代每个值的代码。使用结构是不可能的。
void incrementValues (boost::tuples::null_type) {}
template <typename Tuple_>
void incrementValues (Tuple_ & tuple) {
// ...
++tuple.get_head ();
incrementValues (tuple.get_tail ());
}
答案 6 :(得分:2)
防止您的代码被许多结构定义所困扰。对于编写代码的人来说,以及当你只是记录元组中每个元素的内容时,更容易使用它,而不是编写自己的结构/让人们查找结构定义。
答案 7 :(得分:2)
元组将更容易编写 - 无需为返回某些内容的每个函数创建新的结构。关于将去哪里的功能文档的文档,无论如何都需要。要使用该功能,我们需要在任何情况下阅读功能文档,并在那里解释元组。
答案 8 :(得分:-1)
我同意你100%罗迪。
要从方法返回多个值,除了元组之外,您还有几个选项,其中一个最好取决于您的情况:
创建新结构。当您返回的多个值相关时,这很好,并且适合创建新的抽象。例如,我认为“divide_result”是一个很好的通用抽象,并且传递这个实体使得你的代码比传递一个无名元组更清晰。然后,您可以创建对此新类型进行操作的方法,将其转换为其他数字类型等。
使用“Out”参数。通过引用传递多个参数,并通过分配每个out参数返回多个值。当您的方法返回多个不相关的信息时,这是合适的。在这种情况下创建一个新的结构将是过度的,使用Out参数,你强调这一点,加上每个项目都得到它应得的名称。
元组是邪恶的。