Given the comments I've gotten, I've decided on a different approach to this answer. Here is a program that uses various ways of adding an object to a vector. This program prints out exactly which copy constructors, move constructors, copy operators, move operators, and destructors are called in all cases.
The program uses the marvelous fmt library. And fmt::print
does exactly what you think it does. I should, perhaps, use ::fmt
. Keep in mind that moving from an object leaves it in an unspecified (note, not the same as undefined) state, in general. Of course, for classes you write yourself, you know exactly what state it's in. But for standard library classes, or classes written by other people, you don't. Apparently, for the standard library classes you are guaranteed that if you do something to set the state to something known, that the object will indeed change to that state.
Personally, I would just treat the object as if the only valid operation on it after you move from it is to call its destructor. But I can see cases in which you might simply want to re-use the storage in some way.
Here is the program:
#include <fmt/core.h> // fmt
#include <vector> // vector
#include <utility> // move
class TestClass {
public:
TestClass(int a, int b) {
fmt::print("TestClass{{{}, {}}}\n", a, b);
}
TestClass(TestClass const &) noexcept {
fmt::print("TestClass{{TestClass const &}}\n");
}
TestClass(TestClass &&) noexcept {
fmt::print("TestClass{{TestClass &&}}\n");
}
TestClass const &operator =(TestClass const &) noexcept {
fmt::print("=(TestClass const &)\n");
return *this;
}
TestClass const &operator =(TestClass &&) noexcept {
fmt::print("=(TestClass &&)\n");
return *this;
}
~TestClass() noexcept {
fmt::print("~TestClass()\n");
}
};
int main()
{
::std::vector<TestClass> v;
// Reserve necessary space so movements of vector elements doesn't clutter up
// the output.
v.reserve(6);
fmt::print("Constructing initial\n");
TestClass o{1, 2};
fmt::print("\bv.push_back(o)\n");
v.push_back(o);
fmt::print("\nv.push_back(::std::move(o))\n");
v.push_back(::std::move(o));
fmt::print("\nv.push_back(TestClass{{3, 4}})\n");
v.push_back(TestClass{3, 4});
fmt::print("\nv.emplace_back(5, 6)\n");
v.emplace_back(5, 6);
fmt::print("\nv.emplace_back(::std::move(o))\n");
v.emplace_back(::std::move(o));
fmt::print("\nv.emplace_back(TestClass{{5, 6}})\n");
v.emplace_back(TestClass{5, 6});
fmt::print("\nHere H\n");
}
Here is the program's output:
Constructing initial
TestClass{1, 2}
v.push_back(o)
TestClass{TestClass const &}
v.push_back(::std::move(o))
TestClass{TestClass &&}
v.push_back(TestClass{3, 4})
TestClass{3, 4}
TestClass{TestClass &&}
~TestClass()
v.emplace_back(5, 6)
TestClass{5, 6}
v.emplace_back(::std::move(o))
TestClass{TestClass &&}
v.emplace_back(TestClass{5, 6})
TestClass{5, 6}
TestClass{TestClass &&}
~TestClass()
Here H
~TestClass()
~TestClass()
~TestClass()
~TestClass()
~TestClass()
~TestClass()
~TestClass()
I will say that this program has exactly the output I expected, and that output is (AFAIK) consistent with the answer I had here before.
Why I use ::std::
instead of std::