Are there any methods of procuring an iterator, when working with a Standard Library map container, which don't require searching throughout the container?
I have a managing class for a map, and I wish to return the iterator associated to items added to the map. I don't want to rely upon find() if at all possible. If I can avoid searches I figure all the better.
std::map<char, bool>::iterator ClassA::Add(char item)
{
mymap[item] = false;
return mymap.get_iterator_lastitem();
}
Perhaps
return mymap.end() - 1;
If you're not using C++11, then
std::map<char, bool>::iterator ClassA::Add(char item)
{
std::pair<std::map<char, bool>::iterator, bool> result = mymap.insert(std::make_pair(item, false));
if(!result.second) {
// Item already exists, modify that existing item
result.first->second = false;
}
return result.first;
}
If you are using C++11 then it is better to use emplace + auto.
std::map<char, bool>::iterator ClassA::Add(char item)
{
auto result = mymap.emplace(item, false);
if(!result.second) {
// Item already exists, modify that existing item
result.first->second = false;
}
return result.first;
}
Live example
Both insert and emplace return a pair of an iterator and a boolean, of which the iterator points to the inserted or existing element and the boolean indicates whether an insertion (true) took place or if not (false) of which the returned iterator points to the already-existing element with the key.
Related
I have a factory function that returns a non-owning pointer to the created object after it is inserted into a resource-owning map to be later stored as a non-owning pointer elsewhere; however, by using the return value of std::map::try_emplace which should be an iterator to what was/is inserted, this causes the internal contents of the std::unique_ptr to be invalid when accessed through the referring non-owning pointer.
Instead, I have to call std::map::find and basically search for a value twice.
I don't quite understand what's wrong with the non-working version of the code below.
Shouldn't the return value of std::map::try_emplace be directly usable/copyable?
What I wanted to do, but doesn't work:
std::map<std::string, std::unique_ptr<Feature>> Feature::s_registry{};
Feature* Feature::CreateFeature(Map* map, const XMLElement& elem) {
auto new_feature = std::make_unique<Feature>(map, elem);
//Copy name out so it isn't erased via the move operation.
std::string new_feature_name = new_feature->name;
if(auto&& [where, inserted] = s_registry.try_emplace(new_feature_name, std::move(new_feature)); inserted) {
return where->second.get();
} else {
if(where != std::end(s_registry)) {
return where->second.get();
}
}
return nullptr;
}
Instead I have to call find to get a valid iterator:
std::map<std::string, std::unique_ptr<Feature>> Feature::s_registry{};
Feature* Feature::CreateFeature(Map* map, const XMLElement& elem) {
auto new_feature = std::make_unique<Feature>(map, elem);
//Copy name out so it isn't erased via the move operation.
std::string new_feature_name = new_feature->name;
if(const auto where_inserted = s_registry.try_emplace(new_feature_name, std::move(new_feature)); where_inserted.second) {
if(const auto found = s_registry.find(new_feature_name); found != std::end(s_registry)) {
return found->second.get();
}
} else {
if(const auto found = s_registry.find(new_feature_name); found != std::end(s_registry)) {
return found->second.get();
}
}
return nullptr;
}
The code can be as simple as
Feature* Feature::CreateFeature(Map* map, const XMLElement& elem)
{
auto new_feature = std::make_unique<Feature>(map, elem);
return s_registry.emplace(new_feature->name, std::move(new_feature)).first->second.get();
}
If the new_feature was not inserted because the slot is already occupied, .first points to the already existing value. Otherwise, it points to the newly inserted object. In both cases, that object's ->second should be valid.
Furthermore, std::move does not move anything. The members that new_feature points to can be used until the final destination is initialized, at which point the std::map will already know where to insert the value. Therefore, it is not necessary to keep new_feature->name in a separate value. There is some discussion of this behavior in this post.
I have a similar question here but the context of this new question is different.
Background
I have this variable: PublisherMap m_mapPublishers;
The definition of PublisherMap is:
using PublisherMap = std::map<CString, S_DEMO_ENTRY_EX>;
The code
I have this method that reads the map and populates a CListBox:
bool CChristianLifeMinistryPersonalCopiesDlg::InitPublishersGrid()
{
try
{
m_lbPublishers.ResetContent();
for (auto & mapPublisher : m_mapPublishers)
{
bool bInclude = false;
if (m_iDisplayMode == DISPLAY_EVERYONE)
bInclude = true;
else if (m_iDisplayMode == DISPLAY_BROTHER && mapPublisher.second.eGender == GENDER_MALE)
bInclude = true;
else if (m_iDisplayMode == DISPLAY_SISTER && mapPublisher.second.eGender == GENDER_FEMALE)
bInclude = true;
if (bInclude && m_bLimitDisplay)
{
CString strTemp;
if (!m_mapSSAssignedPublishers.Lookup(mapPublisher.first, strTemp))
bInclude = FALSE;
}
if (bInclude)
{
int i = m_lbPublishers.AddString(mapPublisher.first);
m_lbPublishers.SetItemData(i, MAKEWPARAM(mapPublisher.second.eGender, mapPublisher.second.eAppointed));
}
}
}
catch (_com_error e)
{
LPCTSTR szError = e.ErrorMessage();
AfxMessageBox(szError);
return false;
}
catch (CException* e)
{
e->Delete();
AfxMessageBox(_T("CException"));
return false;
}
m_iSelectMode = SELECT_NONE;
UpdateData(FALSE);
return true;
}
Notice that I use item data:
m_lbPublishers.SetItemData(i,
MAKEWPARAM(mapPublisher.second.eGender, mapPublisher.second.eAppointed));
It works absolutely fine. If I was using a CPtrArray I would have assigned the actual structure object pointers against each entry in the list box.
The question
I don't know the mechanics of std::map enough. Is there any safe way to directly associate each entry from the map (mapPublisher) against each list box entry so that I can later access it?
I realise I could take the text of the list box entry and then find it in the map and get it that way. But if there is a more direct way to tie the two together?
std::map is specified as an associative container that never moves existing elements, see [associative.reqmts]/9:
The insert and emplace members shall not affect the validity of iterators and references to the container, and the erase members shall invalidate only iterators and references to the erased elements.
In practice it's often implemented as a red-black tree.
So it is safe to keep pointers to existing elements, as long as their lifetime exceeds the lifetime of the pointers.
Note you will lose that guarantee if you switch to std::unordered_map (a hash map).
To set:
m_lbPublishers.SetItemDataPtr(i, &mapPublisher.second);
To retrieve:
auto psEntry = (S_DEMO_ENTRY_EX*)m_lbPublishers.GetItemDataPtr(i);
CListBox::GetItemDataPtr() returns void* so a cast is required.
As long as the node of the map isn't destroyed/deleted you can pass a pointer to the mapped datatype directly to the CListBox::SetItemDataPtr.
So in your case accessing the S_DEMO_ENTRY_EX and using a pointer using &mapPublisher.second is OK.
This is guaranteed by the rules for the STL
in java, I sometimes do this
Map<String, POJO> objmap = new HashMap<String, POJO>();
POJO obj = null;
if ((obj = objMap.get(key)) == null) {
obj = new POJO();
objMap.put(key, obj);
}
obj.setName("something");
obj.setAddress("yeah");
What is the best practice to do similar thing in c++ with std::map?
to create a obj in map if not exist, then update its properties?
Like this:
void insert_or_update(const K & k, const T & t, std::map<K, T> & m)
{
auto p = m.insert(std::make_pair(k, t));
if (!p.second) p.first->second = t;
}
Or:
m[k] = t;
The latter requires T to be default-constructible and assignable.
In C++17 you can also say:
m.insert_or_assign(k, t);
This has fewer restrictions than the above construction and returns information on whether the insertion took place, as well as the iterator to the element.
You want to use the insert function, it returns an iterator and a boolean regarding whether a new object was inserted:
something like this:
typedef map<int,void*> M;
M m;
auto insertion = m.insert(M::value_type(0,nullptr));
if (insertion.second) {
insertion.first->second = new... (// allocate your item or whatever, this is the iterator to it)
}
You can write objmap[key] = value.
See: http://www.cplusplus.com/reference/map/map/operator[]/
std::map<std::string, POJO> mapStr2Pojo;
mapStr2Pojo["something"].setName("something");
mapStr2Pojo["something"].setAddress("yeah");
std::map<>'s operation[] inserts the object if it doesn't find it.
the insertion operation checks whether each inserted element has a key equivalent to the one of an element already in the container, and if so, the element is not inserted, returning an iterator to this existing element
if ( !myMap.insert( std::make_pair( key, value ) ).second )
{
// Element already present...
}
How to correctly find element from source map and insert it into another map?
std::map<int, std::shared_prt<Obj>> src_map
std::map<int, std::shared_prt<Obj>> target_map
int key = 6;
auto found_elem = src_map.find(key);
if (found_elem != src_map.end()) {
if (target_map.find(key) == target_map.end()) {
target_map.insert(found_elem ); <---- How to correctly insert found element from src_map to target_map
}
}
target_map.insert(found_elem);
found_elem is an iterator, you need to insert the value it refers to:
target_map.insert(*found_elem);
Also this could be done more efficiently:
if (target_map.find(key) == target_map.end()) {
target_map.insert(found_elem);
}
You do the lookup twice. Once in find and again in insert.
It's better to just try to insert it, and if you need to know whether it was inserted check the return value:
auto inserted = target_map.insert(*found_elem);
// inserted.first is the iterator to the element with the desired key
// inserted.second is true if a new element was inserted, false if the key already existed
Other options for putting it in the map are to find the position where it belongs, then insert at that position if it's not there already:
auto lower = target_map.lower_bound(key);
if (lower == target_map.end() || lower->first != key) {
target_map.insert(lower, *found_elem);
}
Another option is:
auto& val = target_map[found_elem->first];
if (!val)
val = found_elem->second;
but this is not exactly the same, because if the key already exists in the map with an empty shared_ptr as the value then the value will get replaced. Depending whether you can have empty shared_ptr objects in the map that might not be correct for your program.
Yet another, with slightly different meaning again, is:
target_map[found_elem->first] = found_elem->second;
In current declaration
std::map<int, Obj> src_map
std::map<int, Obj> target_map
You can't have one Obj instance in memory connected to both maps. Either you remove Obj from src_map and put in target_map or change declaration to;
std::map<int, Obj*> src_map
std::map<int, Obj*> target_map
or any other pointer type (shared_ptr as suggested in comment), without this you will always have two independent objects in memory.
i got some issues trying to put the values of my vector in a new map (maMap)
If someone could explain me what contain my ItemIterator or how to do...
map<std::string,Employee*> Entreprise::convertiVectorMap() const
{
map<std::string,Employee*> maMap;
vector<Employee*>::const_iterator ItemIterator;
for(ItemIterator = vector_employe.begin(); ItemIterator != vector_employe.end(); ItemIterator++)
{
maMap.insert(std::pair<string,Employee*>(ItemIterator->getNom(),ItemIterator));
}
}
Your map is of <std::string, Employee*>, but you are trying to add an iterator as the second element of the pair. You need to dereference the iterator to get the Employee pointer.
maMap.insert(std::pair<string,Employee*>((*ItemIterator)->getNom(), *ItemIterator));
Or to save from dereferencing the same iterator twice, you could just use a range based for loop. As #CaptainObvlious mentions, you can also use std::make_pair to add to your map.
for(auto const employee: vector_employe)
{
maMap.insert(std::make_pair(employee->getNom(), employee));
}
You forgot to derefrence your iterator:
maMap.insert(std::pair<string,Employee*>((*ItemIterator)->getNom(),*ItemIterator));
And since everyone asks for a revamped version of your code here we go:
map<std::string,Employee*> Entreprise::convertiVectorMap() const
{
map<std::string,Employee*> maMap;
for(vector<Employee*>::const_iterator ItemIterator = vector_employe.cbegin(),
ItemIteratorEnd = vector_employe.cend();
ItmeIterator != ItemIteratorEnd; ++ItemIterator)
{
Employee* ptr = *ItemIterator;
maMap.insert(std::make_pair(ptr->getNom(),ptr));
}
}
You can also use ranged based for if you're at least in C++11.