C++ In-place Construction And Piecewise-Construction
Save whenever you can not whenever you have to — John D. Rockefeller
Imagine we have a LIDAR/Radar map of an unknown territory in a game, or real world robot navigation. On this map, we estimate a map based on how many times each scanned pixel point has been been detected. So, we have a class Pixel2DWithCount
to keep track of the detection count along with the pixel itself.
struct Pixel2DWithCount {
int x, y;
unsigned int total_count_ = 1;
unsigned int hit_count_ = 0;
}
If the the territory is large, we have a huge number of pixels that need to be tracked. Therefore, when storing into a container, we want to construct the new objects in-place. Since C++11, in C++ STL containers, emplace()
or emplace_back()
are functions for this purpose.
emplace(iterator, args&&)
are seen in many STL containers, such as std::unordered_map, std::set, std::vector .
We can pass in position (iterator) in the container for the insertion, and args&&
are used to construct the new object, possibly through move semantics. emplace_back()
are seen in std::vector, std::list, std::deque
.
How To Set Up For Emplace()
In this section, we are trying to add the new object into an std::unordered_map
called count_map
.
First, we need a custom constructor (ctor), because the default aggregation initialization does not work with in-place construction std::unordered_map::emplace(args)
without creating an extra copy
Pixel2DWithCount(int x_, int y_, unsigned int hit_count_)
: x(x_), y(y_), hit_count_(hit_count_) {}
Second, count_map.emplace(j, Rigid2D::Pixel2DWithCount(j, j, 1));
works, because C++ still synthesizes a copy ctor despite a custom one is defined. C++ won’t synthesize the copy ctor / copy assignment operator unless you declare it deleted, or a custom move ctor/move assignment operator is defined.
Then, one could do in-place construction using std::piecewise_construct
(defined in <utility>
).
int main()
{
// int is index
std::unordered_map<int, Pixel2DWithCount> m;
m.emplace(std::piecewise_construct, std::make_tuple(1), std::make_tuple(1,2));
return 0;
}
However, this method requires us to create a tuple for std::make_tuple(1)
, which is unnecessary.
Well, thanks for reading this far. As you might have guessed, alternatively, one can do m.emplace(1, Foo(1,2));
. This makes use of the default move ctor and a new object should be built in place