Erase element from set with const key - c++

Unordered set keys are read only, so why in this case I can erase element:
std::unordered_set<std::string> s;
s.emplace("sth");
s.erase("sth");
and in this not:
std::unordered_set<std::string const> s;
const std::string str("sth");
s.emplace(str);
s.erase(str);
If set itself would be const It would make sense but with const keys I don't quite understand that. This assertion fails:
static_assert(!is_reference<_Tp>::value && !is_const<_Tp>::value, "");
Why would somebody who wrote that assertion, check if key is not const?
EDIT:
In fact, the code above compiles fine for std::set. For std::unordered_set, the failure is directly at instantiation. A minimal example to reproduce:
// define a customized hash ...
int main() { sizeof(std::unordered_set<int const>); }

The reason that you cannot erase the element is not because of const-correctness.
It is because you cannot have a container of const things. It is not permitted.
You broke the contract of unordered_set.
The static_assert detected that.

I think the thing that you're missing is that std::set actually allocates its own strings. so when you say s.emplace("sth"), it creates a new "sth" string, it doesn't use yours (it uses it to construct a new one).
Why are these newly allocated strings const ? Because you're not suppose to modify them directly, or else you'll break the set. If you change "aaa" to "zzz" directly then the set will still think that "zzz" is the first element in the set, before "bbb".
So why is that assert there ?
Because std does not have an allocator for const objects - so when it tries to allocate a const object it will fail.
he VS2017 error is more obvious:
"The C++ Standard forbids containers of const elements because allocator is ill-formed."

Related

Comparator can be used to set a new key, isn't?

I need to change the "key" of a multiset:
multiset<IMidiMsgExt, IMidiMsgExtCompByNoteNumber> playingNotes;
such as that when I use the .find() function it search and return the first object (iterator) with that NoteNumber property value.
I said "first" because my multiset list could contains objects with the same "key". So I did:
struct IMidiMsgExtCompByNoteNumber {
bool operator()(const IMidiMsgExt& lhs, const IMidiMsgExt& rhs) {
return lhs.NoteNumber() < rhs.NoteNumber();
}
};
but when I try to do:
auto it = playingNotes.find(60);
the compiler says no instance of overloaded function "std::multiset<_Kty, _Pr, _Alloc>::find [with _Kty=IMidiMsgExt, _Pr=IMidiMsgExtCompByNoteNumber, _Alloc=std::allocator<IMidiMsgExt>]" matches the argument list
Am I misunderstanding the whole thing? What's wrong?
I do believe that you have some misunderstandings here:
Part of an associative container's type is it's key type and comparator. Because C++ is strongly typed the only way to change the comparator on a container is to create a new container, copying or moving all the elements into it
Creating a copy of all the elements in a container is a potentially expensive process
By creating a copy you are violating the Single Source of Truth best practice
multiset is used infrequently, I have used it once in my career, others have pointed out it's shortcomings and recommended that you use another container, write your own container, or in my case I'd suggests simply using vector and sorting it how you want when you have to
I'm going to catalog your comments to show how the answer I've already given you is correct:
We're going to assume that the multiset<IMidiMsgExt, IMidiMsgExtCompByNoteNumber> that you've selected is necessary and cannot be improved upon by using vector as suggested in 4, where:
struct IMidiMsgExtCompByNoteNumber {
bool operator()(const IMidiMsgExt& lhs, const IMidiMsgExt& rhs) {
return lhs.NoteNumber() < rhs.NoteNumber();
}
};
You cannot use multiset::find because that requires you tospecify the exact IMidiMsgExt you are searching for; so you'll need to use find_if(cbegin(playingNotes), cend(playingNotes), [value = int{60}](const auto& i){return i.mNote == value;}) to search for a specific property value. Which will be fine to use on to use directly on PlayingNotes without changing the sorting, because you say:
I want to delete the first note that has mNote of 60. No matter the mTime when deleting.
You'll need to capture the result of the [find_if], check if it is valid, and if so erase it as demonstrated in my answer, because you say:
The first element find will find for that, erase. [sic]
I would roll the code from my answer into a function because you say:
Ill recall find if I want another element, maybe with same value, to get deleted [sic]
Your final solution should be to write a function like this:
bool foo(const multiset<IMidiMsgExt, IMidiMsgExtCompByNoteNumber>& playingNotes, const int value) {
const auto it = find_if(cbegin(playingNotes), cend(playingNotes), [=](const auto& i){return i.mNote == value;});
const auto result = it != cend(playingNotes);
if(result) {
playingNotes.erase(it);
}
return result;
}
And you'd call it something like this: foo(playingNotes, 60) if you wish to know whether an element was removed you may test foo's return.

BOOST_FOREACH over a vector of pointers

I want to use the BOOST_FOREACH macro to iterate over a bunch of values in a vector of mine. The vector looks like this:
struct _Element
{
int key;
// more variables here
}
elements = new std::vector<_Element *>;
I'm very new to C++, and I'm a bit stumped as to how I would actually iterate over the contained _Element *'s. Why doesn't this work?
BOOST_FOREACH(_Element *currentElem, rootElement->_document->elements)
{
// do stuff
}
Compiling this gives me an error:
shared.cc:146:37: error: no viable conversion from 'std::__1::vector<_Element *, std::__1::allocator<_Element *> >' to '_Element *'
BOOST_FOREACH(_Element *currentElem, rootElement->_document->elements)
~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The type of elements is vector<_Element *>*, so you need to dereference it before passing it to BOOST_FOREACH.
BOOST_FOREACH(_Element *currentElem, *(rootElement->_document->elements))
{
// do stuff
}
That will fix the compilation error, but since you're new to C++, there's a very good chance you don't need all those pointers you've declared. For instance, your code should probably look like this:
struct Element // do not use leading underscore followed by
// uppercase letter, that's reserved
{
int key;
// more variables here
};
std::vector<Element> elements = std::vector<Element>;
// vector of Element, not Element*. And elements is a vector, not vector *
Finally, if you have a compiler that supports C++11's range based for you don't need BOOST_FOREACH.
for(auto&& currentElem : rootElement.document.elements)
// notice that I've gotten rid of all the pointers within rootElement
{
// currentElem is a reference to the current element in the elements vector
}
The statement
elements = new std::vector<_Element *>;
indicates that elements is of pointer type.
Technically that means that you need to dereference it, *elements, for use with BOOST_FOREACH.
But that's still wholly wrong on the level of good programming practice.
Instead:
Let elements just be a vector, directly. Not a pointer.
Don't use new.
Use a C++11 range-based for if your compiler supports it. If not, then upgrade your compiler and use a C++11 range-based for.
It can look like this:
for( auto const& item : elements )
{
// Do whatever
}
or if the items are of small/simple enough type that a bit of value copying doesn't matter, just
for( auto const item : elements )
{
// Do whatever
}
In passing: in addition to avoiding needless huge library dependencies, and avoiding use of raw pointers where practical, you might want to reconsider using a prefix underscore as a member name convention. Prefix underscores are used by a lot of other software and are reserved in the global namespace. A nice alternative is a suffix underscore.
BOOST_FOREACH expects a container not a pointer to a container as the second argument.
Use
BOOST_FOREACH(_Element *currentElem, *(rootElement->_document->elements))
{
// do stuff
}
Do :
BOOST_FOREACH(_Element *currentElem, *(rootElement->_document->elements))
{
.....
}
Or if your compiler supports C++11, prefer using the built-in range iteration:
for(auto element : *(rootElement->_document->elements))
{
....
}

C++ for-each statement triggers "vector iterators incompatible" assertion failure: this->_Getcont() == 0

This is with Visual Studio 2012.
static void func(
...,
const std::vector<std::string> &opt_extra_args_strs,
...)
{
// THIS ASSERTS: "vector iterators incompatible"
for (const std::string &arg_str : opt_extra_args_strs) {
... body does not modify opt_extra_args_strs
// BUT THIS WORKS:
for (size_t a_ix = 0; a_ix < opt_extra_args_strs.size(); a_ix++) {
const std::string &arg_str = opt_extra_args_strs[a_ix];
}
I am not modifying the vector at all in the loop body, and in fact, the assertion happens before the first iteration. The vector looks right in the debugger, but I don't know enough about STL to look for corruption. Inside the STL the assertion failure comes from:
void _Compat(const _Myiter& _Right) const {
// test for compatible iterator pair
if (this->_Getcont() == 0 // THIS FAILS (_Getcont() == 0)
...) {
_DEBUG_ERROR("vector iterators incompatible");
with this->_Getcont() being NULL because (_Myproxy is NULL in the _Iterator_base12).
The call stack is:
msvcp110d.dll!std::_Debug_message(const wchar_t * message, const wchar_t * file, unsigned int line) Line 15 C++
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::_Compat(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::operator==(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::operator!=(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!run_test(..., const std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > & opt_extra_args_strs)
...
I suspect the code setting up the vector is screwing it up somehow, but I am not sure. I am having difficulty writing a simpler reproducer as well, but the program should be totally deterministic (single threaded, not random variables, always asserts).
Additionally, I also run into a different similar assertion failure earlier "vector iterator + offset out of range" with the snippet (on the same vector)
template <typename T>
class Elsewhere {
virtual void earlier(
....
std::vector<T> &v) const
{
v.emplace_back(); // empty construction of a T
// T &t = v.back(); // assertion failure
T &val = to[to.size() - 1]; // but this works
... mutates val.
With T = std::string (in fact, the same vector).
I mention this because within STL the condition for this failure ends up also being this->_Getcont() == 0, and I suspect them to be related. What does it mean for _Getcont() to be 0 in a Vector_const_iterator?
The vector comes out of a container
template <typename T>
struct type {
T m_value;
operator const T &() const {
return value();
}
const T &value() const {
return m_value;
}
};
type<std::vector<std::string>> &t = ... method call that returns ref to it;
... t gets set
func(t); // implicit conversion to (const std::vector<std::string> &)
I finally found the problem. A path deep in the vector's setup code was clobbering the vector's state (memset'ing it to 0's as part of a larger chunk of memory). This caused no harm for the first three fields in vector: _Myfirst, _Mylast, and _Myend since those fields are 0 in an initial vector. Moreover, most things such as the array index operator and other methods such as push_back still functioned correctly. However, the fourth field _Myproxy was initially non-zero, and clearing it disabled iterator-based functionality. Hence, for-each loop, vector::back(), and others all fail with different false errors such as false bounds checks, false incompatible iterators, etc...
Regarding the first item with the range for loop, unless there is something new in the C++11 standard, the incompatible iterator message is 100 percent accurate. That line is attempting to assign a constant string reference to an iterator from the vector. An iterator type is not compatible with the data type of the element being stored.
Please take a look at http://www.stroustrup.com/C++11FAQ.html#for. In that example, the auto datatype is used so that the under the hood setting of the iterator through the colon usage is set at the beginning of the container (e.g. much less keyboard typing than the old days). Then more under the hood things occur that make sure the iterator never passes the last element. The equivalent of the range for loop above (as written) is:
// added a local string to clearly indicate types
std::string s1;
const std::string &arg_str = s1;
const std::vector<std::string> :: iterator i;
i = opt_extra_args_strs.begin(); // happens inside the range for
for (arg_str = i; i < opt_extra_args_strs.end(); i++)
{
// loop body
}
As soon as the arg_str reference is reassigned to the starting point for the iterator, the compiler should complain and print an error.
The second for loop is an older alternative to avoid iterators, but continue to use the other methods available for range checking a dynamic container like a vector, and stay within the container bounds for the quantity of elements currently inside it. That loop has to always work, since the loop body is assigning a locally allocated const string reference to each element (also a string, but not a constant string) within the container. An iterator type is never attempted to be assigned to the string reference inside the second for loop body.
This new range for has many nice features to reduce keyboard typing as much as possible. However, it is probably best to use the auto keyword and subsequent datatype assignments that the compiler will always get right (assuming it remains in C++11 compliance forever).
The assertion failure of
T &t = v.back(); // assertion failure
is also completely correct for an empty vector (Start Edit).
The back() method is a recent addition to the STL for me. I am sorry. In my haste to complete the original post before heading out, I read the word back() and translated that in my brain to end().
If the end() method is called: The T &t is not the same type as std :: vector<T> :: iterator, which is what is returned when v.end() is called. In terms of types it looks more like this:
T &t = std::vector<T> :: iterator
After reviewing the details on std::vector::back(), if back() is called on an empty vector, the behavior is undefined. More than likely, finding the flaw would be only at run time. For reference, try this: http://www.cpluscplus.com/reference/vector/vector/back. For always calling back(), the first thing that has to be confirmed is that at least one element exists inside the vector. Prior to that, since emplace_back(Args&&... args); is the prototype at http://www.cpluscplus.com/reference/vector/vector/emplace_back, calling it with no parameter to insert after the current last element is not defined. The reference page says, "If allocator_traits::construct is not supported with the appropriate arguments, it causes undefined behavior." The code will probably have to become something more like:
//
// start of the 'earlier' function body
//
std::string s;
v.emplace_back(s); // add one element to v
//
// obtain a reference to the last element (should be a copy of s above)
//
T &t = v.back();
//
// It is not clear what the vector 'to' is and how it exists inside 'earlier'
// as long as 'to' has at least one element, then the code below will
// set the local reference variable to the last element of 'to'.
// If not, then another run time error is likely with attempting to access to[-1]
// and then attempting to assign the non-existent element to T& val
//
T &val = to[to.size() - 1];
I hope this helps understand the difference between an iterator, adding an element after the current last element, and the data element type stored inside a container.
(End Edit)
It would be very surprising that the memory assignment area is a problem. Visual Studio 2012 would have a host of unhappy C++ engineers. It is highly recommended to undo whatever modifications occurred inside the stl source code.
I encountered this assert failure myself. I discovered the cause to be that something I was calling within the loop was modifying the vector that I was iterating through.

stl map operator[] bad?

My code reviewers has pointed it out that the use of operator[] of the map is very bad and lead to errors:
map[i] = new someClass; // potential dangling pointer when executed twice
Or
if (map[i]==NULL) ... // implicitly create the entry i in the map
Although I understand the risk after reading the API that the insert() is better of since it checks for duplicate, thus can avoid the dangling pointer from happening, I don't understand that if handled properly, why [] can not be used at all?
I pick map as my internal container exactly because I want to use its quick and self-explaining indexing capability.
I hope someone can either argue more with me or stand on my side:)
The only time (that I can think of) where operator[] can be useful is when you want to set the value of a key (overwrite it if it already has a value), and you know that it is safe to overwrite (which it should be since you should be using smart pointers, not raw pointers) and is cheap to default construct, and in some contexts the value should have no-throw construction and assignment.
e.g. (similar to your first example)
std::map<int, std::unique_ptr<int>> m;
m[3] = std::unique_ptr<int>(new int(5));
m[3] = std::unique_ptr<int>(new int(3)); // No, it should be 3.
Otherwise there are a few ways to do it depending on context, however I would recommend to always use the general solution (that way you can't get it wrong).
Find a value and create it if it doesn't exist:
1. General Solution (recommended as it always works)
std::map<int, std::unique_ptr<int>> m;
auto it = m.lower_bound(3);
if(it == std::end(m) || m.key_comp()(3, it->first))
it = m.insert(it, std::make_pair(3, std::unique_ptr<int>(new int(3)));
2. With cheap default construction of value
std::map<int, std::unique_ptr<int>> m;
auto& obj = m[3]; // value is default constructed if it doesn't exists.
if(!obj)
{
try
{
obj = std::unique_ptr<int>(new int(3)); // default constructed value is overwritten.
}
catch(...)
{
m.erase(3);
throw;
}
}
3. With cheap default construction and no-throw insertion of value
std::map<int, my_objecct> m;
auto& obj = m[3]; // value is default constructed if it doesn't exists.
if(!obj)
obj = my_objecct(3);
Note: You could easily wrap the general solution into a helper method:
template<typename T, typename F>
typename T::iterator find_or_create(T& m, const typename T::key_type& key, const F& factory)
{
auto it = m.lower_bound(key);
if(it == std::end(m) || m.key_comp()(key, it->first))
it = m.insert(it, std::make_pair(key, factory()));
return it;
}
int main()
{
std::map<int, std::unique_ptr<int>> m;
auto it = find_or_create(m, 3, []
{
return std::unique_ptr<int>(new int(3));
});
return 0;
}
Note that I pass a templated factory method instead of a value for the create case, this way there is no overhead when the value was found and does not need to be created. Since the lambda is passed as a template argument the compiler can choose to inline it.
You are right that map::operator[] has to be used with care, but it can be quite useful: if you want to find an element in the map, and if not there create it:
someClass *&obj = map[x];
if (!obj)
obj = new someClass;
obj->doThings();
And there is just one lookup in the map.
If the new fails, you may want to remove the NULL pointer from the map, of course:
someClass *&obj = map[x];
if (!obj)
try
{
obj = new someClass;
}
catch (...)
{
obj.erase(x);
throw;
}
obj->doThings();
Naturally, if you want to find something, but not to insert it:
std::map<int, someClass*>::iterator it = map.find(x); //or ::const_iterator
if (it != map.end())
{
someClass *obj = it->second;
obj->doThings();
}
Claims like "use of operator[] of the map is very bad" should always be a warning sign of almost religious belief. But as with most such claims, there is a bit of truth lurking somewhere. The truth here however is as with almost any other construct in the C++ standard library: be careful and know what you are doing. You can (accidentally) misuse almost everything.
One common problem is potential memory leaks (assuming your map owns the objects):
std::map<int,T*> m;
m[3] = new T;
...
m[3] = new T;
This will obviously leak memory, as it overwrites the pointer. Using insert here correctly isn't easy either, and many people make a mistake that will leak anyways, like:
std::map<int,T*> m;
minsert(std::make_pair(3,new T));
...
m.insert(std::make_pair(3,new T));
While this will not overwrite the old pointer, it will not insert the new and also leak it. The correct way with insert would be (possibly better enhanced with smart pointers):
std::map<int,T*> m;
m.insert(std::make_pair(3,new T));
....
T* tmp = new T;
if( !m.insert(std::make_pair(3,tmp)) )
{
delete tmp;
}
But this is somewhat ugly too. I personally prefer for such simple cases:
std::map<int,T*> m;
T*& tp = m[3];
if( !tp )
{
tp = new T;
}
But this is maybe the same amount of personal preference as your code reviewers have for not allowing op[] usage...
operator [] is avoided for insertion, because for the same reason
you mentioned in your question. It doesn't check for duplicate key
and overwrites on the existing one.
operator [] is mostly avoided for searching in the std::map.
Because, if a key doesn't exist in your map, then operator []
would silently create new key and initialize it (typically to
0). Which may not be a preferable in all cases. One should use
[] only if there is need to create a key, if it doesn't exist.
This is not a problem with [] at all. It's a problem with storing raw pointers in containers.
If your map is like for example this :
std::map< int, int* >
then you lose, because next code snippet would leak memory :
std::map< int, int* > m;
m[3] = new int( 5 );
m[3] = new int( 2 );
if handled properly, why [] can not be used at all?
If you properly tested your code, then your code should still fail the code review, because you used raw pointers.
Other then that, if used properly, there is nothing wrong with using map::operator[]. However, you would probably be better with using insert/find methods, because of possible silent map modification.
map[i] = new someClass; // potential dangling pointer when executed twice
here, the problem isn't map's operator[], but *the lack of smart pointers.
Your pointer should be stored into some RAII object (such as a smart pointer), which imemdiately takes ownership of the allocated object, and ensures it will get freed.
If your code reviewers ignore this, and instead say that you should avid operator[], buy them a good C++ textbook.
if (map[i]==NULL) ... // implicitly create the entry i in the map
That's true. But that's because operator[] is designed to behave differently. Obviously, you shouldn't use it in situations where it does the wrong thing.
Generally the problem is that operator[] implicitly creates a value associated with the passed-in key and inserts a new pair in the map if the key does not occur already. This can break you logic from then on, e.g. when you search whether a certain key exists.
map<int, int> m;
if (m[4] != 0) {
cout << "The object exists" << endl; //furthermore this is not even correct 0 is totally valid value
} else {
cout << "The object does not exist" << endl;
}
if (m.find(4) != m.end()) {
cout << "The object exists" << endl; // We always happen to be in this case because m[4] creates the element
}
I recommend using the operator[] only when you know you will be referencing a key already existing in the map(this by the way proves to be not so infrequent case).
There's nothing wrong with operator[] of map, per se, as long as its
semantics correspond to what you want. The problem is defining what you
want (and knowing the exact semantics of operator[]). There are times
when implicitly creating a new entry with a default value when the entry
isn't present is exactly what you want (e.g. counting words in a text
document, where ++ countMap[word] is all that you need); there are
many other times that it's not.
A more serious problem in your code may be that you are storing pointers
in the map. A more natural solution might be to use a map <keyType,
someClass>, rather than a map <keyType, SomeClass*>. But again, this
depends on the desired semantics; for example, I use a lot of map
which are initialized once, at program start up, with pointers to static
instances. If you're map[i] = ... is in an initialization loop,
executed once at start up, there's probably no issue. If it's something
executed in many different places in the code, there probably is an
issue.
The solution to the problem isn't to ban operator[] (or maps to
pointers). The solution is to start by specifying the exact semantics
you need. And if std::map doesn't provide them directly (it rarely
does), write a small wrapper class which defines the exact semantics you
want, using std::map to implement them. Thus, your wrapper for
operator[] might be:
MappedType MyMap::operator[]( KeyType const& key ) const
{
MyMap::Impl::const_iterator elem = myImpl.find( key );
if ( elem == myImpl.end() )
throw EntryNotFoundError();
return elem->second;
}
or:
MappedType* MyMap::operator[]( KeyType const& key ) const
{
MyMap::Impl::const_iterator elem = myImpl.find( key );
return elem == myImpl.end()
? NULL // or the address of some default value
: &elem->second;
}
Similarly, you might want to use insert rather than operator[] if
you really want to insert a value that isn't already present.
And I've almost never seen a case where you'd insert an immediately
newed object into a map. The usual reason for using new and
delete is that the objects in question have some specific lifetime of
their own (and are not copiable—although not an absolute rule, if
you're newing an object which supports copy and assignment, you're
probably doing something wrong). When the mapped type is a pointer,
then either the pointed to objects are static (and the map is more or
less constant after initialization), or the insertion and removal is
done in the constructor and destructor of the class. (But this is just
a general rule; there are certainly exceptions.)

In STL maps, is it better to use map::insert than []?

A while ago, I had a discussion with a colleague about how to insert values in STL maps. I preferred map[key] = value; because it feels natural and is clear to read whereas he preferred map.insert(std::make_pair(key, value)).
I just asked him and neither of us can remember the reason why insert is better, but I am sure it was not just a style preference rather there was a technical reason such as efficiency. The SGI STL reference simply says: "Strictly speaking, this member function is unnecessary: it exists only for convenience."
Can anybody tell me that reason, or am I just dreaming that there is one?
When you write
map[key] = value;
there's no way to tell if you replaced the value for key, or if you created a new key with value.
map::insert() will only create:
using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
cout << "key " << key << " already exists "
<< " with value " << (res.first)->second << endl;
} else {
cout << "created key " << key << " with value " << value << endl;
}
For most of my apps, I usually don't care if I'm creating or replacing, so I use the easier to read map[key] = value.
The two have different semantics when it comes to the key already existing in the map. So they aren't really directly comparable.
But the operator[] version requires default constructing the value, and then assigning, so if this is more expensive then copy construction, then it will be more expensive. Sometimes default construction doesn't make sense, and then it would be impossible to use the operator[] version.
Another thing to note with std::map:
myMap[nonExistingKey]; will create a new entry in the map, keyed to nonExistingKey initialized to a default value.
This scared the hell out of me the first time I saw it (while banging my head against a nastly legacy bug). Wouldn't have expected it. To me, that looks like a get operation, and I didn't expect the "side-effect." Prefer map.find() when getting from your map.
If the performance hit of the default constructor isn't an issue, the please, for the love of god, go with the more readable version.
:)
insert is better from the point of exception safety.
The expression map[key] = value is actually two operations:
map[key] - creating a map element with default value.
= value - copying the value into that element.
An exception may happen at the second step. As result the operation will be only partially done (a new element was added into map, but that element was not initialized with value). The situation when an operation is not complete, but the system state is modified, is called the operation with "side effect".
insert operation gives a strong guarantee, means it doesn't have side effects (https://en.wikipedia.org/wiki/Exception_safety). insert is either completely done or it leaves the map in unmodified state.
http://www.cplusplus.com/reference/map/map/insert/:
If a single element is to be inserted, there are no changes in the container in case of exception (strong guarantee).
If your application is speed critical i will advice using [] operator because it creates total 3 copies of the original object out of which 2 are temporary objects and sooner or later destroyed as.
But in insert(), 4 copies of the original object are created out of which 3 are temporary objects( not necessarily "temporaries") and are destroyed.
Which means extra time for:
1. One objects memory allocation
2. One extra constructor call
3. One extra destructor call
4. One objects memory deallocation
If your objects are large, constructors are typical, destructors do a lot of resource freeing, above points count even more. Regarding readability, i think both are fair enough.
The same question came into my mind but not over readability but speed.
Here is a sample code through which I came to know about the point i mentioned.
class Sample
{
static int _noOfObjects;
int _objectNo;
public:
Sample() :
_objectNo( _noOfObjects++ )
{
std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
}
Sample( const Sample& sample) :
_objectNo( _noOfObjects++ )
{
std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
}
~Sample()
{
std::cout<<"Destroying object "<<_objectNo<<std::endl;
}
};
int Sample::_noOfObjects = 0;
int main(int argc, char* argv[])
{
Sample sample;
std::map<int,Sample> map;
map.insert( std::make_pair<int,Sample>( 1, sample) );
//map[1] = sample;
return 0;
}
Now in c++11 I think that the best way to insert a pair in a STL map is:
typedef std::map<int, std::string> MyMap;
MyMap map;
auto& result = map.emplace(3,"Hello");
The result will be a pair with:
First element (result.first), points to the pair inserted or point to
the pair with this key if the key already exist.
Second element (result.second), true if the insertion was correct or
false it something went wrong.
PS: If you don´t case about the order you can use std::unordered_map ;)
Thanks!
A gotcha with map::insert() is that it won't replace a value if the key already exists in the map. I've seen C++ code written by Java programmers where they have expected insert() to behave the same way as Map.put() in Java where values are replaced.
One note is that you can also use Boost.Assign:
using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope
void something()
{
map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}
Here's another example, showing that operator[] overwrites the value for the key if it exists, but .insert does not overwrite the value if it exists.
void mapTest()
{
map<int,float> m;
for( int i = 0 ; i <= 2 ; i++ )
{
pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;
if( result.second )
printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
else
printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
}
puts( "All map values:" ) ;
for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
printf( "%d=>%f\n", iter->first, iter->second ) ;
/// now watch this..
m[5]=900.f ; //using operator[] OVERWRITES map values
puts( "All map values:" ) ;
for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
printf( "%d=>%f\n", iter->first, iter->second ) ;
}
This is a rather restricted case, but judging from the comments I've received I think it's worth noting.
I've seen people in the past use maps in the form of
map< const key, const val> Map;
to evade cases of accidental value overwriting, but then go ahead writing in some other bits of code:
const_cast< T >Map[]=val;
Their reason for doing this as I recall was because they were sure that in these certain bits of code they were not going to be overwriting map values; hence, going ahead with the more 'readable' method [].
I've never actually had any direct trouble from the code that was written by these people, but I strongly feel up until today that risks - however small - should not be taken when they can be easily avoided.
In cases where you're dealing with map values that absolutely must not be overwritten, use insert. Don't make exceptions merely for readability.
The fact that std::map insert() function doesn't overwrite value associated with the key allows us to write object enumeration code like this:
string word;
map<string, size_t> dict;
while(getline(cin, word)) {
dict.insert(make_pair(word, dict.size()));
}
It's a pretty common problem when we need to map different non-unique objects to some id's in range 0..N. Those id's can be later used, for example, in graph algorithms. Alternative with operator[] would look less readable in my opinion:
string word;
map<string, size_t> dict;
while(getline(cin, word)) {
size_t sz = dict.size();
if (!dict.count(word))
dict[word] = sz;
}
The difference between insert() and operator[] has already been well explained in the other answers. However, new insertion methods for std::map were introduced with C++11 and C++17 respectively:
C++11 offers emplace() as also mentioned in einpoklum's comment and GutiMac's answer.
C++17 offers insert_or_assign() and try_emplace().
Let me give a brief summary of the "new" insertion methods:
emplace(): When used correctly, this method can avoid unnecessary copy or move operations by constructing the element to be inserted in place. Similar to insert(), an element is only inserted if there is no element with the same key in the container.
insert_or_assign(): This method is an "improved" version of operator[]. Unlike operator[], insert_or_assign() doesn't require the map's value type to be default constructible. This overcomes the disadvantage mentioned e.g. in Greg Rogers' answer.
try_emplace(): This method is an "improved" version of emplace(). Unlike emplace(), try_emplace() doesn't modify its arguments (due to move operations) if insertion fails due to a key already existing in the map.
For more details on insert_or_assign() and try_emplace() please see my answer here.
Simple example code on Coliru