How do you return a list? Best practices

A

Andrew Brampton

Hi,
This may sound a odd question, but I wanted to know how you return a list of
data from a function. These are some of the ways I know how, and I was
wondering which method you normally use. This is more of a best practices
question rather than a technical one.

1) Return a list instance ie
std::list myFunction() {
std::list list();
return list;
}

This however does a lot of copying and isn't very efficient, but seems
conceptually the easiest.

2) Return a list pointer (or reference) ie
std::list * my Function() {
std::list *list = malloc(sizeof(std::list));
return list;
}

This only needs to copy a pointer, however this list must now be deleted. If
we passed the address of say a member variable (or global variable) then we
don't need to delete, but make sure it doesn't change before the caller is
finished using it.

3) Return a iterator ie,
std::list::iterator myFunction() {
std::list list;
return list.begin();
}

This has little copying again, but we also need to know the list.end() maybe
from another method.

4) Pass the list in
void myFunction(std::list &list) {
// Fill the list with stuff
}

Again little copying, sounds a good idea.


Each has pros and cons but this may sound like a silly question, but which
method is most "advisable" from a API point of view? For example if I was
writing a API for other developers to use, which would be the best?

Thanks very much
Andrew

P.S Would the answer to this question be any different if it was a set,
vector, or map?
 
A

Alf P. Steinbach

* Andrew Brampton:
Hi,
This may sound a odd question, but I wanted to know how you return a list of
data from a function. These are some of the ways I know how, and I was
wondering which method you normally use. This is more of a best practices
question rather than a technical one.

1) Return a list instance ie
std::list myFunction() {

std::list list();

That's a declaration of a function 'list' returning a 'std::list'.

return list;
}


With a compiler that does NRVO optimization this (with errors mentioned above
fixed) is the best. Many compilers now do NRVO.

This however does a lot of copying and isn't very efficient

With NRVO it's actually the most efficient.

, but seems conceptually the easiest.

That also.


2) Return a list pointer (or reference) ie
std::list * my Function() {
std::list *list = malloc(sizeof(std::list));
return list;
}

Don't use malloc in C++, use 'new'.

This only needs to copy a pointer, however this list must now be deleted. If
we passed the address of say a member variable (or global variable) then we
don't need to delete, but make sure it doesn't change before the caller is
finished using it.

3) Return a iterator ie,
std::list::iterator myFunction() {
std::list list;
return list.begin();
}

That's VERY BAD: you're returning a pointer to a local variable, and have
Undefined Behavior.


This has little copying again, but we also need to know the list.end() maybe
from another method.

4) Pass the list in
void myFunction(std::list &list) {
// Fill the list with stuff
}

Again little copying, sounds a good idea.

That's essentially what an NRVO optimization does, except the optimization
avoid the original initialization.


Each has pros and cons but this may sound like a silly question, but which
method is most "advisable" from a API point of view? For example if I was
writing a API for other developers to use, which would be the best?

The first, or the first plus the last (with the first as a wrapper for the
last, both offered to the client code).


P.S Would the answer to this question be any different if it was a set,
vector, or map?

No.
 
M

Maxim Yegorushkin

Andrew said:
Hi,
This may sound a odd question, but I wanted to know how you return a list of
data from a function. These are some of the ways I know how, and I was
wondering which method you normally use. This is more of a best practices
question rather than a technical one.

One way to do this is to use intrusive lists. Sketch:

template<class T>
struct list_node
{
list_node* next;
T value;
};

template<class T>
void delete_list(list_node<T>* node)
{
if(node)
{
delete_list(node->next);
delete node;
}
}

list_node<whatever>* create_my_list()
{
list_node<whatever>* head = new list_node<whatever>();
// attach other nodes to head->next
return head;
}
 
D

dag_henriksson

When I want to return a collection, I usually do it like this:

template<typename InIterator>
void GetCollection(InIterator it)
{
// return 1, 2, 3
*it++ = 1;
*it++ = 2;
* it++ = 3;
}

And call it like this:
std::list<int> myList;
GetCollection(std::back_inserter(myList));
 
M

Maxim Yegorushkin

When I want to return a collection, I usually do it like this:

template<typename InIterator>
void GetCollection(InIterator it)
{
// return 1, 2, 3
*it++ = 1;
*it++ = 2;
* it++ = 3;
}

What would you do if this function were going to be exported from a
shared library?
 
E

Earl Purple

Maxim said:
What would you do if this function were going to be exported from a
shared library?

With shared libraries you will always have a problem as none of the STL
collections are portable across libraries, nor is std::string.

(You can use boost::shared_array instead of vector if you can be
certain that everyone is using the same implementation of boost).
 
R

roberts.noah

Earl said:
With shared libraries you will always have a problem as none of the STL
collections are portable across libraries, nor is std::string.

This is often caused by the static linking of the C runtime library.
If you dynamically link to your basic runtime libraries (in ALL of the
libs being used) then everyone will use the same allocators and such
and the problem goes away. Of course this is a platform issue but
there you go...
 
R

Robbie Hatley

Andrew Brampton said:
std::list myFunction() {
std::list list();
return list;
}

Yuck. Wastes time and memory.

std::list * my Function() {
std::list *list = malloc(sizeof(std::list));
return list;
}

EWWW! Leaks memory like a sieve, if you're not very,
very careful.

std::list::iterator myFunction() {
std::list list;
return list.begin();
}

You cant name a list "list"! Also, passing an iterator to
a local that goes out of scope won't work.

void myFunction(std::list &list) {
// Fill the list with stuff
}

Yes. This is the way I always do it. But you still can't
name a list "list"! I don't think. Even if you can get
away with it for some reason, it's a bad idea. I'd write
that:

int MyFunction(std::list<Wombat> & Aardvark)
{
do stuff
maybe change contents of Aardvark
maybe delete or insert elements in Aardvark
maybe sort or re-order Aardvark
do more stuff
if (stuff went well) return 42;
else return 666;
}
 
R

red floyd

Robbie said:
You cant name a list "list"! Also, passing an iterator to
a local that goes out of scope won't work.
So long as his variable isn't in the std:: namespace, why not?
I'll agree, though, that it's a bad idea.
 
A

Andrew Brampton

Well thankyou to all that replied.

Other than my obvious mistakes you have given me some insight into the
"best" ways to do this.

Thanks again
Andrew
 
M

Maxim Yegorushkin

(e-mail address removed) wrote:

[]
This is often caused by the static linking of the C runtime library.
If you dynamically link to your basic runtime libraries (in ALL of the
libs being used) then everyone will use the same allocators and such
and the problem goes away. Of course this is a platform issue but
there you go...

On Windoze only. There is no such problem on Linux.
 
S

swesoc

I think the best way to written a list is for example

const list& myfunction() {

....
return list_xyz;
}
 
M

Maxim Yegorushkin

swesoc said:
I think the best way to written a list is for example

const list& myfunction() {

...
return list_xyz;
}

Yes, this is a good way to return a dangling reference.
 
D

deane_gavin

Read Alf P Steinbach's post in this thread about Named Return Value
Optimisation. Quick summary:
Yuck. Wastes time and memory.

Not with most modern compilers.

Gavin Deane
 
P

Peter Mayne

Read Alf P Steinbach's post in this thread about Named Return Value
Optimisation. Quick summary:




Not with most modern compilers.

Gavin Deane

Is there a reliable way to find out if my compiler does NRVO?

I can think of:

a) Look at the compiler output. (I'd rather not.)

b) Given std::list<MyClass>, add a copy constructor to MyClass that does
a 'std::cout << "I'm being copied." << std::endl;' and watch what
happens. Might this be optimised away, even though NRVO is not happening?

c) Benchmark.

I wouldn't want to do something that is efficient on one compiler and
lousy on another. Hmm, are there circumstances where I might want NRVO
to not happen?

Thanks.

PJDM
 
N

Neil Cerutti

Is there a reliable way to find out if my compiler does NRVO?

I can think of:

a) Look at the compiler output. (I'd rather not.)

b) Given std::list<MyClass>, add a copy constructor to MyClass
that does a 'std::cout << "I'm being copied." << std::endl;'
and watch what happens. Might this be optimised away, even
though NRVO is not happening?

This is not a good test since a compiler might realize that the
contructors have side effects and reject an optimization it would
otherwise have made.
c) Benchmark.

I wouldn't want to do something that is efficient on one
compiler and lousy on another. Hmm, are there circumstances
where I might want NRVO to not happen?

Don't write functions like these except when it is necessary, and
hope for the best. Alternatives are sometimes merely yucky, but
often actually incorrect. For example, do not try to manually
implement the NRVO when writing operator+.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,062
Latest member
OrderKetozenseACV

Latest Threads

Top