Filling std::vector with objects created elsewhere
Asked Answered
E

6

12

I'm trying to fill a std::vector with objects created in a function like so:

class Foo
{
public:
   Foo() { std::cout << "Foo created!\n"; }
   Foo(const Foo& other) { std::cout << "Foo copied!\n"; }
   Foo(Foo&& other) { std::cout << "Foo moved\n"; }
   ~Foo() { std::cout << "Foo destroyed\n"; }
};

static Foo createFoo() 
{
   return Foo();
}

int main()
{
   {
       std::vector<Foo> fooVector;
       fooVector.reserve(2);
       fooVector.push_back(createFoo());
       fooVector.push_back(createFoo());
       std::cout << "reaching end of scope\n";
   }
   std::cin.get();
}

Output:

Foo created!
Foo moved
Foo destroyed
Foo created!
Foo moved
Foo destroyed
reaching end of scope
Foo destroyed
Foo destroyed

How can Foo be destroyed more times than it is created? I don't understand what is happening here, I thought that Foo would be copied, but the copy constructor is not triggered.

What is the best way to fill a std::vector with objects created elsewhere without them being destroyed?

Eryneryngo answered 10/6, 2020 at 15:38 Comment(2)
The objects that you move from also have to be destroyed at some pointProvocative
"without them being destroyed?" - why exactly is this a problem? Your code has well-defined behavior, it's unclear why you need something elseProvocative
W
14

How can Foo be destroyed more times than it is created?

The output shows exactly as many creations, as it shows destructions:

            change -> cumulative total    
Foo created!    +1 -> 1
Foo moved       +1 -> 2
Foo destroyed   -1 -> 1
Foo created!    +1 -> 2
Foo moved       +1 -> 3
Foo destroyed   -1 -> 2
reaching end of scope
Foo destroyed   -1 -> 1
Foo destroyed   -1 -> 0 all objects that were created are now destroyed

I thought that Foo would be copied, but the copy constructor is not triggered.

Each time you pass an rvalue to the constructor. That is why the move constructor is used instead of copy constructor.


What is the best way to fill a std::vector with objects created elsewhere without them being destroyed?

Well, by not destroying the objects that you created elsewhere... But usually you should avoid doing that, since it is typically a memory leak.

If you create two objects elsewhere and two objects in a vector, then you end up with having created 4 objects. If you wanted only two objects, then for example create the objects directly into the vector and nowhere else. Like this for example:

fooVector.emplace_back();
fooVector.emplace_back();
Windblown answered 10/6, 2020 at 15:42 Comment(2)
Thanks for the clear explanation. I understand the move now, I didn't before :)Eryneryngo
@Eryneryngo I added one more clarification to your questions.Windblown
P
8

When you do

fooVector.push_back(createFoo());

First createFoo() creates a temporary Foo object, this is why you see

Foo created!

Then, that object is "moved" into the vector since it is a prvalue. This is why you see

Foo moved

Now you have an object in the vector, but you also have that temporay object that was created, moving doesn't get rid of that object, it just moves its internals into the object in the vector. You still need to destroy that object once it goes out of scoped and that happens at the end of the full expression giving you the

Foo destroyed

output.

Plunger answered 10/6, 2020 at 15:44 Comment(0)
C
4

When you make std::move(obj), the state of the moved object is supposed to be a new state which can be destroyed. This happens usually by transferring the data held by the object to a new object (will be constructed using move ctor). and finally the object we have taken its content will be destroyed too.

Now each move operation will construct a new object and leave the old one in a state to be destroyed, hence you have the right output 4 constructions (2 by default ctor and two by move ctor) and the corresponding 4 destructions.

Comitia answered 10/6, 2020 at 15:46 Comment(0)
I
2

The expected behavior for move semantics is that moving from an object does not destroy it, but rather guts it, leaving behind an empty shell. That shell must still be disposed of at the end of its scope, and that means its destructor will be invoked, as normal. That empty object is expected to be in a "valid but unspecified state": you can still perform any operation that does not have preconditions on it (like executing a destructor, for instance).

If you write a destructor for a type with move semantics, you need to take into account that you might be destroying such an empty object. The destructor probably won't be doing much work in that case, but that depends on your use case.

This ultimately maintains the rule that for each construction, there must be a corresponding destruction, no matter the type of construction.

Isacco answered 10/6, 2020 at 15:45 Comment(2)
"That empty object is expected to be in a "valid but unspecified state", for which the only allowed operation is to call a destructor." I don't recall that being true. e.g. you can move from a string then reset it to ""Froe
Yeah, any operation that doesn't have preconditions can use a moved from object. My rule of thumb is if I can do the operation to a default constructed instance, I can do it to a moved from objectPlunger
S
0

when you use '}' every locals variables created without malloc beetween {} is destroyed, so when you have a destroyer, it is called.

Staley answered 10/6, 2020 at 15:49 Comment(1)
The destroyer is called destructor.Pathognomy
P
0

Check out the output of this ( ͡° ͜ʖ ͡°)

#include <iostream>
#include <vector>
#include <memory>

class Foo
{
public:
    Foo() { std::cout << "Foo created!\n"; }
    Foo(const Foo& other) { std::cout << "Foo copied!\n"; }
    Foo(Foo&& other) { std::cout << "Foo moved\n"; }
    ~Foo() { std::cout << "Foo destroyed\n"; }
};

static Foo* createFoo()
{
    return new Foo();
}

int main()
{
    {
        std::vector<std::unique_ptr<Foo>> fooVector;
        fooVector.reserve(2);
        fooVector.emplace_back(createFoo());
        fooVector.emplace_back(createFoo());
        std::cout << "reaching end of scope\n";
    }
    std::cin.get();
}

If you're a c++ guy and you care about performance, then there's rarely a reson to create a local instance of an object inside of a function such as createFoo(). Use pointers!

Pinna answered 10/6, 2020 at 16:8 Comment(1)
This is not good advice. The function is returning a temporary, so it's perfectly fine. At least, suggest using unique_ptr instead of a raw pointer.Tripartite

© 2022 - 2024 — McMap. All rights reserved.