I have identified four different ways of inserting elements into a std::map
:
std::map function;
function[0] = 42;
function.inse
If you want to overwrite the element with key 0
function[0] = 42;
Otherwise:
function.insert(std::make_pair(0, 42));
If you want to insert element in std::map - use insert() function, and if you want to find element (by key) and assign some to it - use operator[].
For simplify inserting use boost::assign library, like this:
using namespace boost::assign;
// For inserting one element:
insert( function )( 0, 41 );
// For inserting several elements:
insert( function )( 0, 41 )( 0, 42 )( 0, 43 );
As of C++11, you have two major additional options. First, you can use insert()
with list initialization syntax:
function.insert({0, 42});
This is functionally equivalent to
function.insert(std::map<int, int>::value_type(0, 42));
but much more concise and readable. As other answers have noted, this has several advantages over the other forms:
operator[]
approach requires the mapped type to be assignable, which isn't always the case.operator[]
approach can overwrite existing elements, and gives you no way to tell whether this has happened.insert
that you list involve an implicit type conversion, which may slow your code down.The major drawback is that this form used to require the key and value to be copyable, so it wouldn't work with e.g. a map with unique_ptr
values. That has been fixed in the standard, but the fix may not have reached your standard library implementation yet.
Second, you can use the emplace()
method:
function.emplace(0, 42);
This is more concise than any of the forms of insert()
, works fine with move-only types like unique_ptr
, and theoretically may be slightly more efficient (although a decent compiler should optimize away the difference). The only major drawback is that it may surprise your readers a little, since emplace
methods aren't usually used that way.
I just change the problem a little bit (map of strings) to show another interest of insert:
std::map<int, std::string> rancking;
rancking[0] = 42; // << some compilers [gcc] show no error
rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error
the fact that compiler shows no error on "rancking[1] = 42;" can have devastating impact !
Since C++17 std::map offers two new insertion methods: insert_or_assign() and try_emplace(), as also mentioned in the comment by sp2danny.
insert_or_assign()
Basically, insert_or_assign()
is an "improved" version of operator[]. In contrast to operator[]
, insert_or_assign()
doesn't require the map's value type to be default constructible. For example, the following code doesn't compile, because MyClass
does not have a default constructor:
class MyClass {
public:
MyClass(int i) : m_i(i) {};
int m_i;
};
int main() {
std::map<int, MyClass> myMap;
// VS2017: "C2512: 'MyClass::MyClass' : no appropriate default constructor available"
// Coliru: "error: no matching function for call to 'MyClass::MyClass()"
myMap[0] = MyClass(1);
return 0;
}
However, if you replace myMap[0] = MyClass(1);
by the following line, then the code compiles and the insertion takes place as intended:
myMap.insert_or_assign(0, MyClass(1));
Moreover, similar to insert(), insert_or_assign()
returns a pair<iterator, bool>
. The Boolean value is true
if an insertion occurred and false
if an assignment was done. The iterator points to the element that was inserted or updated.
try_emplace()
Similar to the above, try_emplace()
is an "improvement" of emplace(). In contrast to emplace()
, try_emplace()
doesn't modify its arguments if insertion fails due to a key already existing in the map. For example, the following code attempts to emplace an element with a key that is already stored in the map (see *):
int main() {
std::map<int, std::unique_ptr<MyClass>> myMap2;
myMap2.emplace(0, std::make_unique<MyClass>(1));
auto pMyObj = std::make_unique<MyClass>(2);
auto [it, b] = myMap2.emplace(0, std::move(pMyObj)); // *
if (!b)
std::cout << "pMyObj was not inserted" << std::endl;
if (pMyObj == nullptr)
std::cout << "pMyObj was modified anyway" << std::endl;
else
std::cout << "pMyObj.m_i = " << pMyObj->m_i << std::endl;
return 0;
}
Output (at least for VS2017 and Coliru):
pMyObj was not inserted
pMyObj was modified anyway
As you can see, pMyObj
no longer points to the original object. However, if you replace auto [it, b] = myMap2.emplace(0, std::move(pMyObj));
by the the following code, then the output looks different, because pMyObj
remains unchanged:
auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));
Output:
pMyObj was not inserted
pMyObj pMyObj.m_i = 2
Code on Coliru
Please note: I tried to keep my explanations as short and simple as possible to fit them into this answer. For a more precise and comprehensive description, I recommend reading this article on Fluent C++.
First of all, operator[]
and insert
member functions are not functionally equivalent :
operator[]
will search for the key, insert a default constructed value if not found, and return a reference to which you assign a value. Obviously, this can be inefficient if the mapped_type
can benefit from being directly initialized instead of default constructed and assigned. This method also makes it impossible to determine if an insertion has indeed taken place or if you have only overwritten the value for an previously inserted keyinsert
member function will have no effect if the key is already present in the map and, although it is often forgotten, returns an std::pair<iterator, bool>
which can be of interest (most notably to determine if insertion has actually been done).From all the listed possibilities to call insert
, all three are almost equivalent. As a reminder, let's have look at insert
signature in the standard :
typedef pair<const Key, T> value_type;
/* ... */
pair<iterator, bool> insert(const value_type& x);
So how are the three calls different ?
std::make_pair
relies on template argument deduction and could (and in this case will) produce something of a different type than the actual value_type
of the map, which will require an additional call to std::pair
template constructor in order to convert to value_type
(ie : adding const
to first_type
)std::pair<int, int>
will also require an additional call to the template constructor of std::pair
in order to convert the parameter to value_type
(ie : adding const
to first_type
)std::map<int, int>::value_type
leaves absolutely no place for doubt as it is directly the parameter type expected by the insert
member function.In the end, I would avoid using operator[]
when the objective is to insert, unless there is no additional cost in default-constructing and assigning the mapped_type
, and that I don't care about determining if a new key has effectively inserted. When using insert
, constructing a value_type
is probably the way to go.