简体   繁体   中英

How do I return hundreds of values from a C++ function?

In C++, whenever a function creates many (hundreds or thousands of) values, I used to have the caller pass an array that my function then fills with the output values:

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

So, the function will fill the vector output with the values it computes. But this is not really good C++ style, as I'm realizing now.

The following function signature is better because it doesn't commit to using a std::vector , but could use any container:

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

Now, the caller can call with some inserter :

std::vector<int> values; // or could use deque, list, map, ...
computeValues(input, std::back_inserter(values));

Again, we don't commit to using std::vector specifically, which is nice, because the user might just need the values in a std::set etc. (Should I pass the iterator by value or by reference?)

My question is: Is the insert_iterator the right or standard way to do it? Or is there something even better?

EDIT: I edited the question to make it clear that I'm not talking about returning two or three values, but rather hundreds or thousands. (Imagine you have return all the files you find in a certain directory, or all the edges in a graph etc.)

Response to Edit: Well, if you need to return hundreds and thousands if values, a tuple of course would not be the way to go. Best pick the solution with the iterator then, but it's best not use any specific iterator type.


If you use iterators, you should use them as generic as possible. In your function you have used an insert iterator like insert_iterator< vector<int> > . You lost any genericity. Do it like this:

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

Whatever you give it, it will work now. But it will not work if you have different types in the return set. You can use a tuple then. Also available as std::tuple in the next C++ Standard:

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

If the amount of values is variadic and the type of the values is from a fixed set, like (int, bool, char), you can look into a container of boost::variant . This however implies changes only on the call-side. You can keep the iterator style of above:

std::vector< boost::variant<int, bool, char> > data;
computeValues(42, std::back_inserter(data));

You could return a smart pointer to a vector. That should work and no copy of the vector will be made.

If you don't want to keep the smart pointer for the rest of your program, you could simply create a vector before calling the function, and swap both vectors.

Actually, your old method of passing in the vector has a lot to recommend it -- it's efficient, reliable, and easy to understand. The disadvantages are real but don't apply equally in all cases. Are people really going to want the data in an std::set or list? Are they really going to want to use the long list of numbers without bothering to assign it to a variable first (one of the reasons to return something via 'return' rather than a parameter)? Being generic is nice, but there is a cost in your programming time that may not be redeemed.

If you ever have a group of objects, chances are you have at least a few methods that work on that group of objects (otherwise, what are you doing with them?)

If that's the case, it would make sense to have those methods in a class that contain both said objects and methods.

If that makes sense and you have such a class, return it.

I virtually never find myself thinking that I wish I could return more than one value. By the fact that a method should only do one small thing, your parameters and return values tend to have a relationship, and so are more often than not deserving of a class that contains them, so returning more than one value is rarely interesting (Maybe I wished for it 5 times in 20 years--each time I refactored instead, came up with a better result and realized my first attempt was sub-standard.)

One other option is boost::tuple:http://www.boost.org/doc/libs/1_38_0/libs/tuple/doc/tuple_users_guide.html

int x, y;
boost::tie(x,y) = bar();
  • A stadard container works for homogenous objects 9which you can return).
  • The standard library way is to abstract an algorithm from the container and use iterators to bridge the gap between.
  • If you need to pass more than a single type think of structs/classes.

My question is: Is the insert_iterator the right or standard way to do it?

Yes. Otherwise, if you are not going to have at least as many elements in your container as there will be computed values. This is not always possible, specially, if you want to write to a stream. So, you are good.

Your example with insert_iterator won't work, because insert_iterator is a template requiring a container for a parameter. You could declare it

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

or

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

The first will tie you back to a vector<int> implementation, without any obvious advantages over your initial code. The second is less restrictive, but implementing as a template will give you other constraints that might make it a less desirable choice.

I'd use something like

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;
}

No copying overhead in the return or risk of leaking (if you use auto_ptr correctly in the caller).

I'd say your new solution is more general, and better style. I'm not sure I'd worry too much about style in c++, more about usability and efficiency.

If you're returning a lot of items, and know the size, using a vector would allow you to reserve the memory in one allocation, which may or may not be worth it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM