I am trying to gain a better understanding of std::unordered_map::emplace, and I think I understand how copy and move constructors are being utilized if they exist. I have outlined the relevant descriptions for each different usages below. If anyone finds any issues with the descriptions, please let me know.
However, what I am mostly curious about is that, what happens when ONLY the default and user-defined constructors are defined? It looks like the default constructor is not called at all, and the user-defined constructor is only called ONCE, so how is it populating the FooBar member of the newly constructed element in the unordered_map? (I'd imagine the default/user-defined constructor to be called at least twice). Also, if FooBar doesn't define copy and move constructors, are there any behavioral differences for the 3 cases below?
Note: I understand that this is a trivial example where deep copies aren't an issue, so copy/move semantics doesn't really yield any significant gain. I am just using this simplified example to get my point across.
struct FooBar
{
FooBar()
{
printf("Foobar default constructor called\n");
};
FooBar(int* pFoo, int* pBar)
{
m_pFoo = pFoo;
m_pBar = pBar;
printf("Foobar user-defined constructor called\n");
};
FooBar(FooBar & rhs)
{
m_pBar = rhs.m_pBar;
m_pFoo = rhs.m_pFoo;
printf("Foobar copy constructor called\n");
};
FooBar(FooBar && rhs)
{
m_pBar = rhs.m_pBar;
m_pFoo = rhs.m_pFoo;
rhs.m_pBar = nullptr;
rhs.m_pFoo = nullptr;
printf("Foobar move constructor called\n");
};
int* m_pFoo;
int* m_pBar;
};
int _tmain(int argc, _TCHAR* argv[])
{
std::unordered_map<int, FooBar> map;
//template< class... Args >
//std::pair<iterator, bool> emplace(Args&&... args);
// 1.
// Description: A lvalue of foobar1 is temporarily created, initialized, copied (via copy constructor)
// to supply the in-place constructed element's FooBar member, and destroyed
// Output (if both copy and move constructor exist): Foobar user-defined constructor called, Foobar copy constructor called
// Output (if both copy and move constructor don't exist): Foobar user-defined constructor called
{
FooBar foobar1 = {(int*)0xDEADBEEF, (int*)0x01010101};
map.emplace(10, foobar1);
}
// 2.
// Description: A rvalue of bar1 is temporarily created, initialized, moved (via move constructor)
// to supply the in-place constructed element's FooBar member, and destroyed
// Output (if both copy and move constructor exist): Foobar user-defined constructor called, Foobar move constructor called
// Output (if both copy and move constructor don't exist): Foobar user-defined constructor called
map.emplace(20, FooBar{(int*)0xDEADBEEF,(int*)0x01010101});
// 3.
// Description: A lvalue of foobar1 is temporarily created and initialized. It is then
// explicitly converted to a rvalue (via std::move), moved (via move constructor) to supply
// the in-place constructed element's FooBar member, and destroyed
// Output (if both copy and move constructor exist): Foobar user-defined constructor called, Foobar move constructor called
// Output (if both copy and move constructor don't exist): Foobar user-defined constructor called
{
FooBar foobar2 = {(int*)0xDEADBEEF, (int*)0x01010101};
map.emplace(30, std::move(foobar2));
}
return 0;
}
Thanks.
You seem to have some misunderstanding about basic terminology. A default constructor is one that can be called with no arguments. FooBar(int* pFoo, int* pBar)
is not a default constructor.
Moreover, to get your code to compile on gcc and clang, I had to modify your copy-constructor to FooBar(FooBar const& rhs)
.
Now, when you say in the comments that copy/move constructor do not exist, I suspect you're simply removing their definitions. But this does not mean they won't exist, the compiler will implicitly define one for you (note that VS2013 will not implicitly define a move constructor since it's missing that feature).
When you call unordered_map::emplace
, the arguments are forwarded to construct a unordered_map::value_type
object, i.e. a std::pair<const Key, Value>
, so the calls to emplace
end up calling an std::pair
constructor.
In case 1, you've created an object named foobar1
, and the call to emplace
calls
the FooBar
copy constructor.
In case 2, the temporary FooBar
object you've created will be moved, i.e. the FooBar
move constructor is called, assuming one exists. If it doesn't, the copy constructor will be called.
Case 3 is the same as case 2 because by calling std::move
you're allowing the move constructor to be invoked, and for the foobar2
object to be moved.
If you do not want the copy/move constructors to be used when emplacing an object into the map, then use the piecewise construction constructor of std::pair
.
map.emplace(std::piecewise_construct,
std::forward_as_tuple(40),
std::forward_as_tuple((int*)0xDEADBEEF, (int*)0x01010101));
This should print a single line
Foobar default constructor called
Live demo
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