5

I got a bit confused lately about the memory (de)allocation of std::vectors

Lets assume I got normal vector of integer: std::vector<int> intv; When I push_back some int's it grows by time. And when I leave the scope (i.e.) of the function, it gets deallocated without the need of extra calls.

Great. Lets have another example:

struct foo_t{
    std::string bar:
    unsigned int derp;
}
void hurr(){
    std::vector<foo_t> foov;
    foo_t foo;
    foo.bar = "Sup?";
    foo.derp = 1337;
    foov.push_back(foo);
}

Okay. When I call hurr() the vector gets created, a foo_t instance gets created, the instance gets filled and pushed to the vector. So when I leave the function, the vector gets deallocated and the content (here one foo_t) gets deallocated, too?

Next example:

struct foo_t{
    std::string bar:
    unsigned int derp;
}
std::vector<foo_t> hurr(){
    std::vector<foo_t> foov;
    foo_t foo;
    foo.bar = "Sup?";
    foo.derp = 1337;
    foov.push_back(foo);
    return foov;
}

In my understanding, the vector and its contents live in the stack, which gets (eventually) overwritten by time and the vector I have returned and its contents will be useless. Or does it actually returns a copy of the vector with a copy of its contents (requires a Copy-Constructor for the content datatype if its not a POD)?

And something obvious:

struct foo_t{
    std::string bar:
    unsigned int derp;
}
std::vector<foo_t*> hurr(){
    std::vector<foo_t*> foov;
    foo_t foo = new foo_t;
    foo->bar = "Sup?";
    foo->derp = 1337;
    foov.push_back(foo);
    return foov;
}

Now I have to manually iterate over the vector, delete its contents and then I can safely let the vector fall out of scope, right?

Sergey
  • 7,985
  • 4
  • 48
  • 80
bam
  • 954
  • 8
  • 26
  • _"Now I have to manually itterate over the vector, delete its contents and then I can safely let the vector fall out of scope, right?"_ Yes. – πάντα ῥεῖ Nov 01 '16 at 10:02
  • 1
    "Now I have to manually itterate over the vector, delete its contents and then I can safely let the vector fall out of scope, right?" That would invalidate the pointers in the returned vector. Unless that's what you want, and it probably isn't, you shouldn't. – molbdnilo Nov 01 '16 at 10:08
  • @molbdnilo Depends when _Now_ is supposed to happen. – πάντα ῥεῖ Nov 01 '16 at 10:09
  • @πάνταῥεῖ I understood that it happens before the function returns: "iterate over the vector, delete its contents *and then* [...] fall out of scope". – molbdnilo Nov 01 '16 at 10:13
  • @πάνταῥεῖ @molbdnilo "Now" is after I used the vector returned by `hurr()` and its not needed anymore. – bam Nov 01 '16 at 13:42
  • The vector's contents are dynamic storage, not "stack" (no answers mentioned this yet?!) – M.M Nov 03 '16 at 12:20

3 Answers3

3

This example:

struct foo_t{
    std::string bar;
    unsigned int derp;
};
void hurr(){
    std::vector<foo_t> foov;
    foo_t foo;
    foo.bar = "Sup?";
    foo.derp = 1337;
    foov.push_back(foo);
}

After hurv() finished, foov and foo are both freed.

std::vector<foo_t> hurr(){
    std::vector<foo_t> foov;
    foo_t foo;
    foo.bar = "Sup?";
    foo.derp = 1337;
    foov.push_back(foo);
    return foov;
}

the result std::vector<foo_t> of hurr() is valid with 1 foo_t in it and it's valid. The return foov; may call a copy contructor of std::vector<foo_t>, and it have its free to not make that copy, see copy elision

Anyway, from C++11, you can write this:

struct foo_t{
    std::string bar;
    unsigned int derp;
    // we will copy the string anyway, pass-by-value
    foo_t(std::string bar_, unsigned int d_)
        : bar(std::move(bar_)), derp(d_) {}
};
std::vector<foo_t> hurr(){
    std::vector<foo_t> foov;
    // This is better, in place construction, no temporary
    foov.emplace_back("Sup?", 1337);
    // This require a temporary
    foov.push_back(foo_t{"Sup?", 1337});
    return foov;
}

And, for the last example, yes, you have to manually itterate over the vector, delete its contents and then I can safely let the vector fall out of scope when you no longer want to use the result of hurr(), (not in hurr())

Danh
  • 5,916
  • 7
  • 30
  • 45
  • you missed the point of `emplace_back`. It's whole point is to construct in-place, avoiding the need of a temporary. You should do `emplace_back("sup", 1337)` – bolov Nov 01 '16 at 10:16
  • @bolov actually, it can't be compiled with `emplace_back("sup", 1337)`, `emplace_back` need a constructor, right? – Danh Nov 01 '16 at 10:19
  • yes it does, but `emplace_back` will do the same as `push_back` in this case, i.e. a move a temporary into the vector. – asu Nov 01 '16 at 10:22
  • @Danh yes, it can and that's how it should be used: `emplace_back("sup", 1337)`. Yes, it needs a constructor. It uses the appropriate constructor of the `value_type` to construct the object **in place**. Think of it as getting some arguments and then passing those arguments to the constructor of `value_type`. If you pass it `foo_t{"Sup?", 1337}` then you already create a temporary and then `emplace_back` basically calls the copy or move ctor of `value_type`, which is the same as push_back. – bolov Nov 01 '16 at 10:24
  • I like the usage of c++11 here with a constructor and a copy constructor for the struct. Anyway, is there a difference between a struct and a all public class anymore if I add the constructors to the struct? Which would be preferable? – bam Nov 01 '16 at 13:47
  • @BenediktMokroß AFACT, there is no difference, see [C++ concepts: PODType](http://en.cppreference.com/w/cpp/concept/PODType), except that, you don't have a parameterless contructor, (from C++11 you can add it again by ctor() = default, though) – Danh Nov 01 '16 at 14:42
  • @Danh In your last example `bar_` is copied twice. Did you mean `foo_t(std::string bar_, unsigned int d_) : bar(std::move(bar_)), derp(d_) {}`? – Serikov Nov 03 '16 at 11:43
  • @Danh I think that second copy (in initialization list) can't be elided, [for example, see this answer](http://stackoverflow.com/a/33873333/4899740). – Serikov Nov 03 '16 at 12:01
3
foov.push_back(foo);

Actually, you constructed a foo_v and you pushed it back, which actually created a new foo_v and called the copy constructor with foov as a paremeter. Use emplace_back if you want to avoid this.

return foov;

The compiler may optimize this using return value optimization. See this short program I've made running on coliru as an example. Refer to the other excellent answers in this question.

std::vector<foo_t*> foov;
/* add elements to foov with new */

Now I have to manually itterate over the vector, delete its contents and then I can safely let the vector fall out of scope, right?

Yes, you do. For the same reasons

int* a = new int();

Will not delete a; when a dies.

Community
  • 1
  • 1
asu
  • 1,875
  • 17
  • 27
2

So when I leave the function, the vector gets deallocated and the content (here one foo_t) gets deallocated, too?

Yes. And if foo_t had nontrivial destructor, it would be called.

Or does it actually returns a copy of the vector with a copy of its contents (requires a Copy-Constructor for the content datatype if its not a POD)?

Yes, in this case it returns a copy. Modern compilers are likely to call copy constructor for std::vector, which, in turn, will call copy constructor of the contained type for each element. C++17 introduces guaranteed return value optimization (RVO), so the copy constructor of your vector will not be called. Nevertheless, if you set high optimization level, modern compiler may use RVO too.

Now I have to manually iterate over the vector, delete its contents and then I can safely let the vector fall out of scope, right?

Yes, you are right. Consider using smart pointers, if you do not want to iterate manually.

Sergey
  • 7,985
  • 4
  • 48
  • 80
  • In my [test program](http://coliru.stacked-crooked.com/a/3442c38380fbe573), g++ in `-O0` *actually* seems to make use of RVO. *edit: * i actually didn't compile in `-O0` in that program, but if you specify it you get the same results. – asu Nov 01 '16 at 10:11
  • Usage of smartpointers was another Example for my question which I left out. – bam Nov 01 '16 at 13:48
  • Even if there's no copy elision, the move-constructor will be used (if the contained type is nothrow-moveable) – M.M Nov 03 '16 at 12:19