3

I have to define a function in C++ that generates a vector of classes Foo and returns it.

I can define it in different ways, that are all quite equivalent:

C style parameter passing:

void generateFooVector(vector<Foo> * result)
{
    for (int i = 0; i < 100; i++)
    {
        Foo f = Foo();
        result->push_back(f);
    }
}
int main()
{

    vector<Foo> result;
    generateFooVector(&result);
}

Reference passing passing:

void generateFooVector(vector<Foo> & result)
{
    for (int i = 0; i < 100; i++)
    {
        Foo f = Foo();
        result->push_back(f);
    }
}
int main()
{
    vector<Foo> result;
    generateFooVector(result);
}

Instancing the vector and returning the pointer:

vector<Foo> * generateFooVector()
{
    vector<Foo> * result = new vector<Foo>();
    for (int i = 0; i < 100; i++)
    {
        Foo f = Foo();
        result->push_back(f);
    }
    return result;
}
int main()
{
    vector<Foo> * result = generateFooVector();
}

C style passing and instancing Foo class:

void generateFooVector(vector<Foo *> * result)
{
    for (int i = 0; i < 100; i++)
    {
        Foo * f = new Foo();
        result->push_back(f);
    }
}
int main()
{
    vector<Foo *> result;
    generateFooVector(&result);
}

Reference passing + Foo instances:

void generateFooVector(vector<Foo *> & result)
{
    for (int i = 0; i < 100; i++)
    {
        Foo * f = new Foo();
        result->push_back(f);
    }
}
int main()
{

    vector<Foo *> result;
    generateFooVector(result);
}

Vector instance + Foo instances:

vector<Foo*> * generateFooVector()
{
    vector<Foo*> * result = new vector<Foo*>();
    for (int i = 0; i < 100; i++)
    {
        Foo * f = new Foo();
        result->push_back(f);
    }
    return result;
}
int main()
{

    vector<Foo *> * result = generateFooVector();
}

Passing by value:

vector<Foo> generateFooVector()
{
    vector<Foo> result;
    for (int i = 0; i < 100; i++)
    {
        Foo f = Foo();
        result->push_back(f);
    }
    return result;
}
int main()
{

    vector<Foo > result = generateFooVector();
}

And maybe there are lots of other solutions.

All the current ways have different implications on how the memory is allocated, in the scope.

Maybe I've a lack of theory, but I'm really confused on how can I decide which is the better alternative for each use case, and why one is better than another. Can you help me?

ProGM
  • 6,949
  • 4
  • 33
  • 52
  • Note: use `std::generate`. – chris Mar 11 '14 at 11:30
  • possible duplicate of [how to "return an object" in C++](http://stackoverflow.com/questions/3350385/how-to-return-an-object-in-c) – juanchopanza Mar 11 '14 at 11:30
  • Hundred push_backs? Did you hear about [resize](http://en.cppreference.com/w/cpp/container/vector/resize) and [fill](http://en.cppreference.com/w/cpp/algorithm/fill)? – Tacet Mar 11 '14 at 11:39
  • 1
    @Tacet, Or `reserve` and `std::back_inserter`. `std::fill` isn't useful if you need to call the function every time, but perhaps that's the case here. – chris Mar 11 '14 at 11:45
  • @chris I understand that it's important, but it was just an example. That is not the point. I'm asking about how to return a collection of elements. – ProGM Mar 11 '14 at 11:49

5 Answers5

8

Just do

vector<Foo> generateFooVector()
{
    return vector<Foo>(100);
}

It's called modern c++.

billz
  • 44,644
  • 9
  • 83
  • 100
5

That function is already defined in the Standard Library and is called the constructor of std::vector<Foo>. You can call it as (C++11 style)

auto v = std::vector<Foo>(n); // create n default constructed Foo() objects

or in C++98 style (also valid in C++11) as

std::vector<Foo> v(n); 

I would not try to wrap this.

  • first, you don't want to hide magic numbers like 100 inside it instead of inside a parameter list (template or runtime).
  • another reason not to wrap a constructor is to avoid confusion on behalf of your readers. If I were to encounter a generateVector function, the first question would be: "is this function doing anything else/extra compared to a plain constructor call?"
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • OK, but what is the point of the `auto` here? Is there anything to be gained over the "normal" way of instantiating the object? – juanchopanza Mar 11 '14 at 11:38
  • it's [Sutter's AAA](http://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/) (almost always auto) style that he recommends for C++11. One advantage is that all variable declarations/definitions have the same left-to-right form. – TemplateRex Mar 11 '14 at 11:40
  • Yeah, I know about AAA. But in this case it looks like an anti-pattern. – juanchopanza Mar 11 '14 at 11:41
  • @juanchopanza I think anti-pattern is too strong, since there is no efficiency loss or anything, but only a matter of readability. Anyway, I updated to reflect that the old style is still valid in C++11. – TemplateRex Mar 11 '14 at 11:42
  • I guess it will take some getting used to, or will get discarded. But it does place extra requirements on the type: it must be copyable or move-copyable. – juanchopanza Mar 11 '14 at 11:47
  • @juanchopanza I don't think so, there are no extra requirements on `Foo` here. Copy elision or move semantics on `std::vector` is just assigning another pointer to the allocated memory and nulling out the original one, not actually doing something with the `Foo` objects. – TemplateRex Mar 11 '14 at 11:51
  • The copy or move-copy constructor must be accessible, even if the copy gets elided. Anyway, it isn't a big issue. – juanchopanza Mar 11 '14 at 11:52
  • @juanchopanza ah I see, and that is more than the container requirements for `Foo` to be compatible with `vector`? What kind of types can be inserted in `vector` but not moved/copied? – TemplateRex Mar 11 '14 at 11:55
  • I was talking about `auto t = T(x)` in general, not in the particular case of `std::vector`. It is an idiom that places more requirements than `T t(x);`. – juanchopanza Mar 11 '14 at 11:56
1

I really see two principal different implementations - vector<Foo*> and vector<Foo>. All other are the variations of parameter passing.

As you know, vector copies elements when adding and calls elements destructors when vector is deleted. So in the case of vector<Foo*> you are avoiding copying of objects which can gain if objects are very large. But you're responsible for deleting the memory. So it would be better to store smart pointers with auto deletion, say vector<shared_ptr<Foo>> or vector<unique_ptr<Foo>> (here move logic required). The type of the pointer depends on your application logic.

BTW,

 Foo* pFoo = new Foo;  // also default constructor call

(do you know the difference with new Foo() ?)

 vector<Foo> v(n);  // init vector with n Foo objects calling default ctor 
Community
  • 1
  • 1
Spock77
  • 3,256
  • 2
  • 30
  • 39
0

I would probably take the reference passing approach taken above.

Generally in C and C++ you do not want to return objects that need to be deallocated later because you can cause leaks.

doron
  • 27,972
  • 12
  • 65
  • 103
0

I think it depends a lot on the actual usage - most critically how heavy is foo? If foo is a light-weight object AND a POD-like type then I would use

Vector<Foo> generateFoo();

otherwise perhaps something like:

Vector<Foo*> generateFoo();

might be more useful.

The standard type are efficient and mostly like to be copied so their is little advantage to the vector* solutions. In general it is probably unwise to return (or use) a raw pointer that requires a later delete - so easy to generate leaks within this kind of architecture. So perhaps look at the various smart pointer options for your foo object.

Elemental
  • 7,365
  • 2
  • 28
  • 33
  • This pointer/smart pointer approach is over-complicating things for no reason. – juanchopanza Mar 11 '14 at 11:37
  • if foo is a large or expensive object (say an open file buffer) then you have to use some kind of pointer (as making copies is not practical). – Elemental Mar 11 '14 at 12:37
  • No, because, more likely than not, the copy will be elided. So you should only play complicated pointer tricks *if* your compiler/platform cannot perform the copy elision and *if* you don't have a cheap move constructor. – juanchopanza Mar 11 '14 at 12:41