如何从C++函数返回数百个值?

4

在C++中,每当一个函数创建许多(数百或数千个)值时,我过去常常要求调用者传递一个数组,然后我的函数将输出值填充到该数组中:

void computeValues(int input, std::vector<int>& output);

因此,该函数将使用计算出的值填充向量output。但是,我现在意识到这并不是真正良好的C++风格。

以下函数签名更好,因为它不承诺使用std::vector,而可以使用任何容器:

void computeValues(int input, std::insert_iterator<int> outputInserter);

现在,调用者可以使用一些“插入器”进行调用:
std::vector<int> values; // or could use deque, list, map, ...
computeValues(input, std::back_inserter(values));

再次强调,我们不会承诺专门使用std::vector,这很好,因为用户可能只需要std::set等中的值。(我应该通过值还是引用传递iterator?)

我的问题是:使用insert_iterator是正确或标准的方法吗?还是有更好的方法?

编辑:我编辑了问题,以明确我不是在谈论返回两个或三个值,而是数百或数千个值。(想象一下你需要返回某个目录中找到的所有文件,或者图中的所有边缘等情况。)


为什么不直接让函数返回一个std::vector<int>呢?为什么一定要使用“out”参数? - Eric Petroelje
如果向量包含许多值,则仅返回它太昂贵了。它将在本地创建,然后在返回时复制。我们希望避免在向量周围复制。 - Frank
我不会太担心这个问题(C++1x与右值引用相关,我相信MSVC10和G++4.4都将支持它,从而消除了昂贵的复制)。但是我认为输出迭代器既灵活又高效。想象一下你有一个deque、vector、array等容器,输出迭代器都可以使用。 - Johannes Schaub - litb
1
我认为不去关注这个是懒散/粗心的表现。好的库比如说就不会这样做。避免不必要且有可能耗费巨大代价的复制操作,这是很好的代码风格。 - Frank
9个回答

7
对编辑的回应: 如果你需要返回数百或数千个值,元组显然不是最佳选择。最好选择迭代器解决方案,但最好不要使用特定的迭代器类型。

如果您使用迭代器,请尽可能通用地使用它们。在您的函数中,您使用了一个插入迭代器,如 insert_iterator< vector<int> >。您失去了任何通用性。请像这样操作:

template<typename OutputIterator>
void computeValues(int input, OutputIterator output) {
    ...
}

无论你给它什么,它现在都可以工作。但是如果返回集合中有不同的类型,则它将无法工作。你可以使用元组来解决这个问题。在下一个C++标准中也可以使用std::tuple

boost::tuple<int, bool, char> computeValues(int input) { 
    ....
}

如果值的数量是可变的,而且值的类型来自一个固定的集合,比如(int、bool、char),那么你可以看一下 boost::variant 容器。然而,这仅意味着在调用方面进行更改。你可以保持上述的迭代器风格:
std::vector< boost::variant<int, bool, char> > data;
computeValues(42, std::back_inserter(data));

6
你可以返回指向 vector 的智能指针。这应该是有效的,而且不会复制 vector。如果你不想在程序的其余部分保留智能指针,你可以在调用函数之前创建一个 vector,并交换两个 vector。

3
实际上,您传递向量的旧方法有很多值得推荐的地方--它高效、可靠且易于理解。缺点是真实存在的,但并不在所有情况下都适用。人们真的想要将数据放入std::set或列表中吗?他们真的想使用长列表中的数字而不必先将其分配给变量吗(通过“返回”而不是参数返回某些东西的原因之一)?通用性很好,但在编程时间上会付出代价,这可能无法挽回。

2
如果您有一组对象,那么很可能您至少有几种适用于该组对象的方法(否则,您在做什么?)
如果是这种情况,将这些方法放在包含所述对象和方法的类中是有意义的。
如果这有意义并且您有这样的类,请返回它。
我实际上从来没有想过我希望能够返回多个值。由于方法应该只做一件小事,您的参数和返回值往往具有关系,因此更常见的是值得一个包含它们的类,因此返回多个值很少有意思(也许我在20年中希望5次 - 每次我都进行重构,得出了更好的结果,并意识到我的第一次尝试是次优的)。

1

boost::tie很好用,但请看我的更新:它真正适用于数百个或更多的值。 - Frank

1

你使用 insert_iterator 的示例不会起作用,因为 insert_iterator 是一个需要容器参数的模板。你可以声明它

void computeValues(int input, std::insert_iterator<vector<int> > outputInserter);

或者

template<class Container>
void computeValues(int input, std::insert_iterator<Container> outputInserter);

第一种方法将会将您绑定到一个vector<int>实现中,没有任何明显优势比起您最初的代码。第二种方法则不那么限制,但是作为模板实现可能会给您带来其他约束,这可能会使其成为不太理想的选择。


1

我会使用类似这样的东西

std::auto_ptr<std::vector<int> > computeValues(int input);
{
   std::auto_ptr<std::vector<int> > r(new std::vector<int>);
   r->push_back(...) // Hundreds of these
   return r;
}

在返回中没有复制开销或泄露风险(如果调用者正确使用auto_ptr)。


1
  • 一个标准的容器适用于同类对象(可以返回)。
  • 标准库的方式是从容器中抽象出算法,使用迭代器来弥合差距。
  • 如果需要传递多个类型,请考虑结构体/类。

我的问题是:insert_iterator是正确或标准的方法吗?

是的。否则,如果你的容器中元素的数量不能至少与计算值相等。这并不总是可能的,特别是如果你想写入流中。所以,没问题。


0

我认为你的新解决方案更通用,风格更好。在C++中,我不确定我会过于担心风格,而是更关注可用性和效率。

如果你要返回很多项,并且知道大小,使用vector可以让你在一个分配中保留内存,这可能值得一试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接