I recently stumbled upon some strange behaviour (strange from my point of view) from move constructor. The result is different when compiling with GCC and Visual Studio. I would like to hear the explanation of this behaviour, don't think this is a bug but probably compiler specific.
Consider the following code:
#include <iostream>
#include <unordered_map>
struct Test
{
std::unordered_map<int, int> v;
std::unordered_map<int, int>::iterator vend;
Test(std::unordered_map<int, int>::iterator &it)
: vend { v.end() }
{
it = this->vend;
};
Test() = delete;
Test(Test const &) = delete;
Test(Test &&) = default; // <- line in question
};
int main()
{
std::unordered_map<int, int>::iterator it;
std::unordered_map<int, Test> m;
m.emplace(0, Test{ it });
std::cout << std::boolalpha << (m.at(0).v.end() == it) << "\n";
return 0;
}
So I am storing iterator to the end of map in map element upon creation of the element. I also take a reference to it to compare later. From std::unordered_map::emplace:
Inserts a new element into the container constructed in-place with the given args if there is no element with the key in the container.
Careful use of emplace allows the new element to be constructed while avoiding unnecessary copy or move operations.
Using default move constructor, the iterator stored in map element and my reference are the same:
Test(Test &&) = default;
Results are true
in GCC and true
in VS. Now if I change move constructor to:
Test(Test &&) {}
GCC still returns true
but VS returns false
Just in case tried with c++17, same results. So could anyone please explain what is happening here?
In this line:
m.emplace(0, Test{ it });
... the newly inserted Test
object is constructed from std::forward<Test>(Test{ it })
, so the move constructor is indeed called (because of the forwarding, copy elision does not work here). If you want to construct Test
directly, you can use m.emplace(0, it)
instead.
Now we can see
With Test(Test &&) = default;
, the temporary object designated by Test{ it }.v
is moved into m.at(0).v
. If it
remains valid (this is not guaranteed), m.at(0).v.end() == it
evaluates to true
; otherwise the program results in undefined behavior.
With Test(Test &&) {}
, m.at(0).v
is value-initialized, and it
is invalidated as the temporary object designated by Test{ it }.v
is destroyed. The program results in undefined behavior.
In libstdc++'s implementation, the end iterators for different unordered_map
s have the same value (similar to std::istream_iterator
), so the behavior of GCC is reasonable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With