Re: What's your preferred way of returning a list of items?
On 13.05.2010 00:40, * James Kanze:
On May 12, 11:40 am, "Alf P. Steinbach"<al...@start.no> wrote:
On 12.05.2010 10:18, * DeMarcus:
Here are a couple of ways to return some list of items.
struct A
{
};
std::vector<A> aList; // Some list of items.
// Returning a copy.
std::vector<A> getList() { return aList; }
This one is OK, and will be efficient with modern compiler.
That depends on the context and the compiler. I've found that
in many contexts, if the vector is large, it's not efficient
with g++ or VC++ (or Sun CC).
Huh, that's news to me.
I'm pretty skeptical about that claim since both g++ and MSVC do RVO
optimization by default, but I'll try to remember to check this.
If you're talking about comparing it with a situation where you can avoid
reallocation of the vector's buffer then that doesn't depend on the compiler.
But if you can use better
compilers, maybe.
Anyway, this is clearly the preferred solution until the
profiler says otherwise.
Yah, agreed.
void getList( std::vector<A>& v )
{
std::copy( aList.begin(), aList.end(), v.begin() );
}
This one's signature is OK as an opt-in alternative to the
first one (i.e. provide /both/, or just the first one).
However, the implementation is incorrect unless you assume
that the argument is of exactly the right size for the result
(and that assumption would be unrealistic, to put it mildly).
You could write it like
void getList( std::vector< A>& v )
{
std::vector< A>( aList.begin(), aList.end() ).swap( v );
}
Or simply use clear and a back_inserter. Or resize the target
first. The optimal solution will depend on context.
I like this way as a "default" for this function signature because it guarantees
to get rid of the old buffer allocation (so that one does not hang on to the
largest buffer size forever), and because it's simplest.
void getList( std::vector<A>* v )
{
std::copy( aList.begin(), aList.end(), v->begin() );
}
This one is just bad. Why would you want to support
nullpointer argument? If someone calls 'getList' it's in order
to get that list, not in order to do nothing.
The choice depends on the local coding conventions.
Yes, but one simply shouldn't work for Google as a C++ programmer. :-)
Anyway, under normal assumptions the implementation is incorrect.
// Returning a reference to aList.
const std::vector<A>& getList() { return aList; }
This one's OK if you're clear on what it does.
It's OK if you actually have an aList somewhere, and you want to
return it.
I think you mean, if the lifetime of 'aList' is sufficient. In the OP's example
it is.
const std::vector<A>::const_iterator& getList()
{
return aList.begin();
}
This is one is just silly, the caller can't do anything
reasonable with the returned iterator.
Do you know more ways to return a list?
How about ways to return lists, instead of vectors (arrays)?
What's your preferred way to return a list of items?
That doesn't make sense without a lot more explanation of
exactly what you mean by "list" and in what context you'd want
to return -- what?
Yes and no. The "preferred" way of returning something
(anything, including a list of whatever) is to return it. You
only vary from that when the profiler says you must. In which
case, you have the context, and you adopt the solution most
suited to the context.
This seems to conflate two different meanings of "return". For example, if you
have a pointer p you might return *p, what's "it" that's being returned? So as I
wrote, in order to answer the OP's question it would need to more details.
Also, here comes another trickier one. Let's say I have a
map instead and want to return the keys.
std::map<std::string, A> aMap;
// Returning a copy of the keys.
std::vector<std::string> getList()
{
std::vector<std::string> aKeys;
auto keysEnd = aMap.end();
for( auto i = aMap.begin(); i != keysEnd; ++i )
aKeys.push_back( (*i).first );
return aKeys;
}
void getList( std::vector<std::string>& v )
{
auto keysEnd = aMap.end();
for( auto i = aMap.begin(); i != keysEnd; ++i )
v.push_back( (*i).first );
}
void getList( std::vector<std::string>* v )
{
auto keysEnd = aMap.end();
for( auto i = aMap.begin(); i != keysEnd; ++i )
v->push_back( (*i).first );
}
// But is it even possible to return a reference to
// the keys in a map?
const std::vector<std::string>& getList() { /* What here? */ }
const std::vector<std::string>::const_iterator& getList()
{
/* What here? */
}
How about defining an iterator that walks through the keys.
Then you don't have to create all those string objects.
A transforming iterator. Boost has some good code which will
allow you to do this very simply. Of course, you'll need two
functions: listBegin and listEnd, rather than just one, but this
does allow the most freedom for the user.
I didn't know about the Boost adaptors, I'll look into it (later); thanks!
Cheers,
- Alf
--
blog at <url: http://alfps.wordpress.com>