I am trying to optimize some part of a C++ code that is taking a long time (the following part of the code takes about 19 seconds for X amount of data, and I am trying to finish the whole process in less than 5 seconds for the same amount of data - based on some benchmarks that I have). I have a function "add" that I have written and copied the code here. I will try to explain as much as possible that I think is needed to understand the code. Please let me know if I have missed something.
The following function add is called X times for X amount of data entries.
void HashTable::add(PointObject vector) // PointObject is a user-defined object
{
int combinedHash = hash(vector); // the function "hash" takes less than 1 second for X amount of data
// hashTableMap is an unordered_map<int, std::vector<PointObject>>
if (hashTableMap.count(combinedHash) == 0)
{
// if the hashmap does not contain the combinedHash key, then
// add the key and a new vector
std::vector<PointObject> pointVectorList;
pointVectorList.push_back(vector);
hashTableMap.insert(std::make_pair(combinedHash, pointVectorList));
}
else
{
// otherwise find the key and the corresponding vector of PointObjects and add the current PointObject to the existing vector
auto it = hashTableMap.find(combinedHash);
if (it != hashTableMap.end())
{
std::vector<PointObject> pointVectorList = it->second;
pointVectorList.push_back(vector);
it->second = pointVectorList;
}
}
}
You are doing a lot of useless operations... if I understand correctly, a simplified form could be simply:
void HashTable::add(const PointObject& vector) {
hashTableMap[hash(vector)].push_back(vector);
}
This works because
A map when accessed using operator[] will create a default-initialized value if it's not already present in the map
The value (an std::vector) is returned by reference so you can directly push_back the incoming point to it. This std::vector will be either a newly inserted one or a previously existing one if the key was already in the map.
Note also that, depending on the size of PointObject and other factors, it could be possibly more efficient to pass vector by value instead of by const PointObject&. This is the kind of micro optimization that however requires profiling to be performed sensibly.
Instead of calling hashTableMap.count(combinedHash) and hashTableMap.find(combinedHash), better just insert new element and check what insert() returned:
In versions (1) and (2), the function returns a pair object whose
first element is an iterator pointing either to the newly inserted
element in the container or to the element whose key is equivalent,
and a bool value indicating whether the element was successfully
inserted or not.
Moreover, do not pass objects by value, where you don't have to. Better pass it by pointer or by reference. This:
std::vector<PointObject> pointVectorList = it->second;
is inefficient since it will create an unnecessary copy of the vector.
This .count() is totally unecessary, you could simplify your function to:
void HashTable::add(PointObject vector)
{
int combinedHash = hash(vector);
auto it = hashTableMap.find(combinedHash);
if (it != hashTableMap.end())
{
std::vector<PointObject> pointVectorList = it->second;
pointVectorList.push_back(vector);
it->second = pointVectorList;
}
else
{
std::vector<PointObject> pointVectorList;
pointVectorList.push_back(vector);
hashTableMap.insert(std::make_pair(combinedHash, pointVectorList));
}
}
You are also performing copy operations everywhere. Copying an object is time consuming, avoid doing that. Also use references and pointers when possible:
void HashTable::add(PointObject& vector)
{
int combinedHash = hash(vector);
auto it = hashTableMap.find(combinedHash);
if (it != hashTableMap.end())
{
it->second.push_back(vector);
}
else
{
std::vector<PointObject> pointVectorList;
pointVectorList.push_back(vector);
hashTableMap.insert(std::make_pair(combinedHash, pointVectorList));
}
}
This code can probably be optimized further, but it would require knowing hash(), knowing the way hashTableMap works (by the way, why is it not a std::map?) and some experimentation.
If hashTableMap was a std::map<int, std::vector<pointVectorList>>, you could simplify your function to this:
void HashTable::add(PointObject& vector)
{
hashTableMap[hash(vector)].push_back(vector);
}
And if it was a std::map<int, std::vector<pointVectorList*>> (pointer) you can even avoid that last copy operation.
Without the if, try to insert an empty entry on the hash table:
auto ret = hashTableMap.insert(
std::make_pair(combinedHash, std::vector<PointObject>());
Either a new blank entry will be added, or the already present entry will be retrieved. In your case, you don't need to check which it the case, you just need to take the returned iterator and add the new element:
auto &pointVectorList = *ret.first;
pointVectorList.push_back(vector);
Assuming that PointObject is big and making copies of it is expensive, std::move is your friend here. You'll want to ensure that PointObject is move-aware (either don't define a destructor or copy operator, or provide a move-constructor and move-assignment operator yourself).
void HashTable::add(PointObject vector) // PointObject is a user-defined object
{
int combinedHash = hash(vector); // the function "hash" takes less than 1 second for X amount of data
// hashTableMap is an unordered_map<int, std::vector<PointObject>>
if (hashTableMap.count(combinedHash) == 0)
{
// if the hashmap does not contain the combinedHash key, then
// add the key and a new vector
std::vector<PointObject> pointVectorList;
pointVectorList.push_back(std::move(vector));
hashTableMap.insert(std::make_pair(combinedHash, std::move(pointVectorList)));
}
else
{
// otherwise find the key and the corresponding vector of PointObjects and add the current PointObject to the existing vector
auto it = hashTableMap.find(combinedHash);
if (it != hashTableMap.end())
{
std::vector<PointObject> pointVectorList = it->second;
pointVectorList.push_back(std::move(vector));
it->second = std::move(pointVectorList);
}
}
}
Using std::unordered_map doesn't seem appropriate here - you use the int from hash as the key (which presumably) is the hash of PointObject rather than PointObject itself. Essentially double hashing. And also if you need a PointObject in order to compute the map key then it's not really a key at all! Perhaps std::unordered_multiset would be a better choice?
First define the hash function form PointObject
namespace std
{
template<>
struct hash<PointObject> {
size_t operator()(const PointObject& p) const {
return ::hash(p);
}
};
}
Then something like
#include <unordered_set>
using HashTable = std::unordered_multiset<PointObject>;
int main()
{
HashTable table {};
PointObject a {};
table.insert(a);
table.emplace(/* whatever */);
return 0;
}
Your biggest problem is that you're copying the entire vector (and every element in that vector) twice in the else part:
std::vector<PointObject> pointVectorList = it->second; // first copy
pointVectorList.push_back(vector);
it->second = pointVectorList; // second copy
This means that every time you're adding an element to an existing vector you're copying that entire vector.
If you used a reference to that vector you'd do a lot better:
std::vector<PointObject> &pointVectorList = it->second;
pointVectorList.push_back(vector);
//it->second = pointVectorList; // don't need this anymore.
On a side note, in your unordered_map you're hashing your value to be your key.
You could use an unordered_set with your hash function instead.
Related
my task is to overload the [] operator and use girl[index] = partner to write stuff into a pair vector:
class Dancers {
unsigned long & operator [] (int i);
vector<pair<int,string>> m_dancers;
};
unsigned long & operator [] (int i) {
auto iter = lower_bound(m_dancers.first.begin(), m_dancers.first.end(), i, cmpInt);
m_dancers.first.insert(iter, i);
//what now?
}
int main() {
Pairs girl;
girl[0] = "Richard";
return 0;
}
So I've managed to sort the girls and now I have the girl that I want to assign a partner to. From what I understand, now it's time to return the reference so I can assign the partner. How do I do that using the iterator?
And MORE IMPORTANTLY: is there a more efficient way to assign x and y to a pair-vector in a a[x] = y situation? Or am I trying to reinvent a wheel?
Presumably you don't want to insert a new element if there is already an existing key in your map (i.e. you want a unqiue-key map, not a multi-key map). So you need to check the key and only insert conditionally. And you want to return the mapped element, not they key.
string & operator[](int key) {
auto it = lower_bound(m_dancers.begin(), m_dancers.end(), key, cmpInt);
if (it->first != key)
it = m_dancers.insert(it, make_pair(i, string()));
return it->second;
}
If you wanted a multi-key map instead, then just omit the conditional check and make the insertion unconditionally. (But then you'd probably want to use upper_bound so that new elements are added at the end of their equal-range, also see here.)
To summarize, things that needed to be fixed in your code:
Return type
Iterators are from the vector, not from the pair
Insertion is conditional
Remember the result of the insertion
You misspelled your use case, it should say Dancers girl;
You are probably misspelling the out-of-line member definition; it should say string & Dancers::operator[](int i)... (or just define it inline).
I have vector of some data type (Let's say-int) and I need to push back only unique values from the file? I am new to use STL. So i don't know how can i do it using map as i read that map only takes unique values. If I simply push back, then it will take all the values irrespective of its uniqueness.
The correct container to use for unique values is either std::set or std::unordered_set:
std::set<int> s;
s.insert(4); // s has size 1
s.insert(5); // s has size 2
s.insert(4); // s still has size 2
If you want to use vector, you'd have to maintain it sorted, which is a lot more code and work, and doesn't have the nice characteristic of set that everybody knows the contents are unique:
void add_value(std::vector<int>& v, int value) {
// do a binary search to find value
std::vector<int>::iterator it = std::lower_bound(v.begin(), v.end(), value);
if (it != v.end() && *it == value) {
// duplicate - do nothing
}
else {
// insert our value here
v.insert(it, value);
}
}
... or I guess you could delete the duplicates at the end using a rarely-used algorithm (std::unique) that will probably raise some eyebrows:
void uniqify(std::vector<int>& v) {
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());
}
[UPDATE] It has been pointed out to me that I completely misunderstood your question - and that you may have been looking for just which values occur exactly once - not a list of which values occur without duplicate. For that, the correct container to use is either a std::map or std::unordered_map - so you can associate a count with a particular key:
std::map<int, int> keyCounts;
int value;
while (fileStream >> value) { // or whatever
++keyCounts[value]; // operator[] gives us a reference to the value
// if it wasn't present before, it'll insert a default
// one - which for int is zero - so this handles
// both cases correctly
}
// Now, any key with value 1 is a unique key
// what you want to do with them is up to you
// e.g., let's put it in a vector
std::vector<int> uniq;
uniq.reserve(keyCounts.size());
for (std::map<int, int>::iterator it = keyCounts.begin(); it != keyCounts.end(); ++it)
{
if (it->second == 1) {
uniq.push_back(it->first);
}
}
A std::map will let you handle a mapping of unique keys to some values (which may or may not be unique). Math-wise, You may see it as a surjective function from the set of keys to the set of values of your dataset.
If your goal is to keep unique indices (or keys), then std::map is what you need. Otherwise, use std::set to store unique values.
Now, to keep only unique values from your dataset, you basically want to remove values which appear more than once. The simplest algorithm is to add values from the file as keys in a map, with its corresponding value being a counter for the number of occurrences of that entry in the file. Initialize a counter to 1 the first time the value is met in the file, and increment it each time it is met again. After having parsed the whole file, simply keep the keys whose values are exactly 1.
Counting the values:
template <typename key>
void count(std::istream &is, std::map<key,int> &map){
while (!is.eof() && is.good()){
key << is;
auto it = map.find(key);
if (it == map.end())
map[key] = 1;
else (*it)++;
}
}
The above assumes that the << has been overloaded to extract values from the stream sequentially. You will have to adapt the algorithm to fit your own way of parsing the data.
Filtering the resulting map to keep unique values can be achieved with std::remove_if and a function returning true when the counter is above 1:
The function:
bool duplicate (std::const_iterator<int> &it){ return *it > 1;}
The map filtering:
std::remove_if (map.begin(), map.end(), duplicate);
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.)
I'm using the STL map data structure, and at the moment my code first invokes find(): if the key was not previously in the map, it calls insert() it, otherwise it does nothing.
map<Foo*, string>::iterator it;
it = my_map.find(foo_obj); // 1st lookup
if(it == my_map.end()){
my_map[foo_obj] = "some value"; // 2nd lookup
}else{
// ok do nothing.
}
I was wondering if there is a better way than this, because as far as I can tell, in this case when I want to insert a key that is not present yet, I perform 2 lookups in the map data structures: one for find(), one in the insert() (which corresponds to the operator[] ).
Thanks in advance for any suggestion.
Normally if you do a find and maybe an insert, then you want to keep (and retrieve) the old value if it already existed. If you just want to overwrite any old value, map[foo_obj]="some value" will do that.
Here's how you get the old value, or insert a new one if it didn't exist, with one map lookup:
typedef std::map<Foo*,std::string> M;
typedef M::iterator I;
std::pair<I,bool> const& r=my_map.insert(M::value_type(foo_obj,"some value"));
if (r.second) {
// value was inserted; now my_map[foo_obj]="some value"
} else {
// value wasn't inserted because my_map[foo_obj] already existed.
// note: the old value is available through r.first->second
// and may not be "some value"
}
// in any case, r.first->second holds the current value of my_map[foo_obj]
This is a common enough idiom that you may want to use a helper function:
template <class M,class Key>
typename M::mapped_type &
get_else_update(M &m,Key const& k,typename M::mapped_type const& v) {
return m.insert(typename M::value_type(k,v)).first->second;
}
get_else_update(my_map,foo_obj,"some value");
If you have an expensive computation for v you want to skip if it already exists (e.g. memoization), you can generalize that too:
template <class M,class Key,class F>
typename M::mapped_type &
get_else_compute(M &m,Key const& k,F f) {
typedef typename M::mapped_type V;
std::pair<typename M::iterator,bool> r=m.insert(typename M::value_type(k,V()));
V &v=r.first->second;
if (r.second)
f(v);
return v;
}
where e.g.
struct F {
void operator()(std::string &val) const
{ val=std::string("some value")+" that is expensive to compute"; }
};
get_else_compute(my_map,foo_obj,F());
If the mapped type isn't default constructible, then make F provide a default value, or add another argument to get_else_compute.
There are two main approaches. The first is to use the insert function that takes a value type and which returns an iterator and a bool which indicate if an insertion took place and returns an iterator to either the existing element with the same key or the newly inserted element.
map<Foo*, string>::iterator it;
it = my_map.find(foo_obj); // 1st lookup
my_map.insert( map<Foo*, string>::value_type(foo_obj, "some_value") );
The advantage of this is that it is simple. The major disadvantage is that you always construct a new value for the second parameter whether or not an insertion is required. In the case of a string this probably doesn't matter. If your value is expensive to construct this may be more wasteful than necessary.
A way round this is to use the 'hint' version of insert.
std::pair< map<foo*, string>::iterator, map<foo*, string>::iterator >
range = my_map.equal_range(foo_obj);
if (range.first == range.second)
{
if (range.first != my_map.begin())
--range.first;
my_map.insert(range.first, map<Foo*, string>::value_type(foo_obj, "some_value") );
}
The insertiong is guaranteed to be in amortized constant time only if the element is inserted immediately after the supplied iterator, hence the --, if possible.
Edit
If this need to -- seems odd, then it is. There is an open defect (233) in the standard that hightlights this issue although the description of the issue as it applies to map is clearer in the duplicate issue 246.
In your example, you want to insert when it's not found. If default construction and setting the value after that is not expensive, I'd suggest simpler version with 1 lookup:
string& r = my_map[foo_obj]; // only lookup & insert if not existed
if (r == "") r = "some value"; // if default (obj wasn't in map), set value
// else existed already, do nothing
If your example tells what you actually want, consider adding that value as str Foo::s instead, you already have the object, so no lookups would be needed, just check if it has default value for that member. And keep the objs in the std::set. Even extending class FooWithValue2 may be cheaper than using map.
But If joining data through the map like this is really needed or if you want to update only if it existed, then Jonathan has the answer.
I'm trying to insert some value pairs into a std::map.
In the first case, I receive a pointer to the map, dereference it and use the subscript operator to assign a value. i.e.
(*foo)[index] = bar;
Later, when I try to iterate over the collection, I'm returned key/value pairs which contain null for the value attribute in all cases except for the first (map.begin()) item. The weird thing is, if I do the insertion via the map's insert function, everything is well, i.e:
foo->insert(std::pair<KeyType,ValueType>(myKey, myValue));
Why would this be? Aren't the two methods functionally equivalent? I've pasted some snippets of actual code below for context
...
typedef std::map<int, SCNode*> SCNodeMap;
...
void StemAndCycle::getCycleNodes(SCNodeMap* cycleNodes)
{
(*cycleNodes)[root->getId()] = root;
SCNode* tmp = root->getSucc();
while(tmp->getId() != root->getId())
{
// (*cycleNodes)[tmp->getId()] == tmp; // crashes (in loop below)
cycleNodes->insert(std::pair<int, SCNode*>(tmp->getId(), tmp));//OK
std::pair<int, SCNode*> it = *(cycleNodes->find(tmp->getId()));
tmp = tmp->getSucc();
}
// debugging; print ids of all the SCNode objects in the collection
std::map<int, SCNode*>::iterator it = cycleNodes->begin();
while(it != cycleNodes->end())
{
std::pair<int, SCNode*> p = (*it);
SCNode* tmp = (*it).second; // null except for it = cycleNodes->begin()
std::cout << "tmp node id: "<<tmp->getId()<<std::endl;
it++;
}
}
I'm all out of ideas. Does anyone have a suggestion please?
In your actual code you have:
(*cycleNodes)[tmp->getId()] == tmp;
This will not assign tmp into the map, but will instead reference into the map creating an empty value (see #Neil Butterworth) - you have == instead of =. What you want is:
(*cycleNodes)[tmp->getId()] = tmp;
You should be aware that operator[] for std::map will insert a value into the map if one does not exist when used in expressions like this:
if ( amap[x] == 42 ) {
...
}
If the value x does not exist, one will be created and assigned the value created by the value types default constructor, or zero for the built-in types. This is almost never what you want, and you should generally avoid the use of operator[] with maps.
Does your value type have an assignment operator?
Take a look at this reference. The [] operator returns a non-const reference to the value. If your assignment is wrong or somehow works in an unexpected way this might be the cause.
The insert method on the other hand takes a value and stuffs it into the map. The [] operator constructs an object with the default constructor, then let's you assign stuff to it using it's assignment operator.