Hello again! In this video, we are going to look emplacement. Emplacement came in, in C++11.
It provides us with another way to insert elements into containers.
So, why is this needed? With the existing interface - functions like insert() and push_back()
- these assume that you already have an object of the correct type for the container, and it will copy
this object into the new element.
And that is fine if you already have an object of the right type and you are going to use it again later.
Quite often, though,
when you are populating a container object, you just have some data and you do not have the right kind
of object.
So this means that you have to create an object of the right type, then insert it, and then you have an
object which you are not going to use again.
With emplacement, the container will actually create the new object directly in the new element.
So this avoids a call to the copy constructor. And if you have a large container, that may make a
noticeable difference.
So to clarify this a bit,
let's say we have a vector of some objects, and we want to insert a new object, with these arguments
to the constructor.
We can create a local variable, and pass the constructor arguments when we declare it.
And then when we call insert(), we give it the iterator where we are going to add the element, and this
local variable.
And then we have this variable, which we are probably not going to use again.
Alternatively, we can pass a constructor call as the second argument to insert.
So this will create a temporary object, which gets destroyed when the insert() call returns.
So this is slightly better, but we still have this copy operation, which we want to avoid. With
emplace(),
We pass the arguments to the constructor as part of the emplace() call.
So we have the iterator,
and then the constructor arguments.
So the vector will create a new element, then it will call the constructor for "refrigerator", with
these as the arguments to the constructor. And that will create a new object in the new element, avoiding
any copying.
So here is some code for that. We have our refrigerator class, which we've used before.
We have a vector of these refrigerators, which is initially empty.
We are going to add some elements.
First, we create a local variable and call insert().
Then we call insert() with a constructor call.
So these are both going to create an object and then copy it into the new element. And then we are
going to call emplace().
So this will create a new element and then create a new object, which will use these as arguments to
the constructor.
And then we have a vector of three elements.
So these were created using a copy.
And this one was created without making a copy.
So we can use emplace instead of insert. There is also an emplace_back() which we can use instead of
push_back().
And that does something similar. So we can pass the constructor arguments to emplace_back().
This will create a new element at the back of the container, and it will create an object, in that new
element, with these arguments to the constructor.
There's also emplace_front() which you can use instead of push_front(). For the container adaptors with
push(), you can use emplace().
So here is the same code again, but we are using push_back() and emplace_back() instead of insert() and
emplace().
And we get the same results.
Unfortunately, there is a slight oddity in the way that emplacement is specified
if the container has unique keys. So this means map and set. In this case emplace() will always create
a temporary object.
Then it will check if there is already an element in the container which has that key.
And if there is not, then it will insert that object.
So that is not really very efficient.
C++17 fixes this by providing a new function called try_emplace(), and this does the obvious thing.
It checks for duplicates before creating any objects.
And if there already is an element with the same key, then nothing will happen.
So what happens if it does need to create a new element? The elements of a map are a pair, so there are
actually two members that needs to be initialized, "first" and "second".
And these are initialized by calling the constructors. And that is known as "piece-wise construction".
The first argument to try_emplace() will be the key of the new element, and the rest of the arguments
will be forwarded to the constructor of the value member.
The return value from try_emplace() is the same as for insert(). So we get an iterator and a bool.
So now we are using a map and some C++ 17 features.
The key to the map is a string, and the value is a refrigerator.
So we do something similar.
We insert a local variable, insert_or_assign(). And then we call insert_or_assign() with a constructor call.
So these are both going to perform a copy, into the new element. And then we call try_emplace(), and
this will create the key, in place, and the value, in place.
So there we are.
So how useful is this? If you have a container of objects which cannot be copied or (we will cover this later!)
moved, then emplacement is the only way that you can populate your container.
The only way to insert elements. There are some circumstances in which emplacement will cause a temporary
object to be created.
If the container does not allow duplicates, which we have just discussed.
If the implementation uses assignments rather than copying for populating its elements. Or if a type
conversion is required. If you have a type conversion, then there must be an object to convert!
Modern compilers are actually very good at optimizing away copies of temporary objects.
And we also have move semantics in C++11, which often means you can avoid copying temporary objects
altogether.
So this would probably have been very useful in C++98, but in C++11, it is, probably, a bit doubtful.
Although it does have one good use case.
Okay, so that is it for this video.
I will see you next time, but until then, keep coding!