Why I cannot use Java-Style QMapIterator in QMap.erase()? - c++

I'm trying this code in Qt 5.12:
//Definition of map
QMap<int, ZoneData*> mapId2Pointer_;
//Some values filled in mapId2Pointer_ here...
QMapIterator<int, ZoneData*> iteratorId2Pointer(mapId2Pointer_);
while (iteratorId2Pointer.hasNext())
{
iteratorId2Pointer.next();
if (iteratorId2Pointer.value() == _climbZoneData)
{
iteratorId2Pointer = mapId2Pointer_.erase(iteratorId2Pointer); //<- ERROR
}
}
But, in the last line of code compiler gives this error:
error: no matching function for call to 'QMap::erase(QMapIterator&)'
iteratorId2Pointer = mapId2Pointer_.erase(iteratorId2Pointer);
I've seen a lot of examples and googled a lot, with no success. All examples look similar to the above one, every one uses a QMapIterator as parameter in QMap::erase() call. But no conversion from QMapIterator to QMap::iterator seems to be possible in my code.
What can be the reason of this error?

QMapIterator is a "Java-style iterator" and QMap::iterator is a "STL-style iterator". They are not compatible; you need to use one or the other.
You can iterate using a QMap::iterator like this:
QMap<int, ZoneData*>::iterator i = mapId2Pointer_.begin();
while(i != mapId2Pointer_.end()) {
if (i.value() == _climbZoneData) {
i = mapId2Pointer_.erase(i);
} else {
++i;
}
}
Note that if you weren't erasing things in the middle (which invalidates your iterator, requiring you to set it to the return value from erase), you could use a for loop instead.

Related

C++ CppCheck algorithm suggestion (std::find_if instead of raw loop) pertinence

CppCheck suggest me to replace one of my code by a STL algorithm, I'm not against it, but I don't know how to replace it. I'm pretty sure this is a bad suggestion (There is warning about experimental functionalities in CppCheck).
Here is the code :
/* Cutted beginning of the function ... */
for ( const auto & program : m_programs )
{
if ( program->compare(vertexShader, tesselationControlShader, tesselationEvaluationShader, geometryShader, fragmentShader) )
{
TraceInfo(Classname, "A program has been found matching every shaders.");
return program;
}
}
return nullptr;
} /* End of the function */
And near the if condition I got : "Consider using std::find_if algorithm instead of a raw loop."
I tried to use it, but I can't get the return working anymore... Should I ignore this suggestion ?
I suppose you may need to use that finding function not once. So, according to DRY, you need to separate the block where you invoke an std::find_if algorithm to a distinct wrapper function.
{
// ... function beginning
auto found = std::find_if(m_programs.cbegin(), m_programs.cend(),
[&](const auto& prog)
{
bool b = prog->compare(...);
if (b)
TraceInfo(...);
return b;
});
if (found == m_programs.cend())
return nullptr;
return *found;
}
The suggestion is good. An STL algorithm migth be able to choose an appropriate
approach based on your container type.
Furthermore, I suggest you to use a self-balancing container like an std::set.
// I don't know what kind of a pointer you use.
using pProgType = std::shared_pointer<ProgType>;
bool compare_progs(const pProgType &a, const pProgType &b)
{
return std::less(*a, *b);
}
std::set<std::shared_pointer<prog_type>,
std::integral_constant<decltype(&compare_progs), &compare_progs>> progs.
This is a sorted container, so you will spend less time for searching a program by a value, given you implement a compare operator (which is invoked by std::less).
If you can use an stl function, use it. This way you will not have to remember what you invented, because stl is properly documented and safe to use.

Correct way to call a member function via a QHash with member function pointers

I have a Server class that processes QJsonObject data and handles it according to a key set in the data.
At the moment, I use a big if-then-else statement to decide what to do like this:
const QString action = jsonObject.value(KEY_ACTION).toString();
if (action == SOME_ACTION) {
// do something
} else if (action == SOME_OTHER_ACTION) {
// do something else
}
and so on. Now, meanwhile, I have quite a lot of actions, and for each one, my server has to check all cases until it finds the correct one. I thus wondered if there was a nicer way to do this.
I thought about having the data processing in different functions and having a QHash with the respective function pointer to the respective function for each action like this:
In the constructor:
const QHash<QString, void(Server::*)(const QJsonObject &)> processFunctionsMap {
{ SOME_ACTION, &Server::processSomeAction },
{ SOME_OTHER_ACTION, &Server::processSomeOtherAction }
}
And the respective functions:
void Server::processSomeAction(const QJsonObject &data)
{
...
}
and then to invoke the matching function:
if (! processFunctionsMap.contains(action)) {
// Catch this case
}
(this->*processFunctionsMap.value(action))(jsonObject);
This seems to work, but I'm not a C++ pro, so my question is if this is the correct way to do it.
Your approach is reasonable, but you've changed the no-matches scenario from executing an else block (potentially doing nothing at all) to instant undefined behavior.
You need to separate the hash lookup from the call so you can insert a check for successful lookup in between. With C++ standard collections (std::map which is a red-black tree, std::unordered_map which is a hashtable), that'd be a call to find(key) which returns an iterator... you compare it to map.end() and make very sure not to dereference if they are equal. QHash, or any other non-standard hashtable, will surely provide something similar.
Understanding what QHash does when key not found

C++ pointer to class in a kind

This is a snippet of an open source code. Full source code is available https://github.com/gec/dnp3/blob/master/src/opendnp3/DNP3/ResponseContext.h
ObjectWriteIterator owi = arAPDU.WriteContiguous(apObject, start,stop);
for(size_t i = start; i <= stop; ++i) {
if(owi.IsEnd()) { // out of space in the fragment
this->mStaticWriteMap[arKey] =
boost::bind(&ResponseContext::WriteStaticObjects<T>, this, apObject,
arStart, arStop, arKey, _1); return false;
}
apObject->Write(*owi, arStart->mValue);
++arStart; //increment the iterators
++owi;
}
ObjectWriteIterator::ObjectWriteIterator() :
mpPos(NULL),
mIndex(1),
mStart(0),
mStop(0),
mObjectSize(0)
{}
My question is: I don't understand is where *owi is referring in this context.
owi is an iterator, which is a 'standard' C++ interface for iterating over some collection.
The interface has them use pointer-symantics, so the * operator 'dereferences' the iterator and returns a reference to the value it currently 'points' to, and incrementing it via ++ moves it to the next item in the collection being iterated over.
In this case, it looks like a collection of ObjectWrite objects inside the collection specified by apObject between start and stop (start and stop are also typically defined by iterators set to some location in the collection).
sorry, I was earlier not sure about one can build a self contained 'Mock up' iterator class which use hidden is the header file
inline boost::uint8_t* ObjectWriteIterator::operator*() const
{
if(this->IsEnd()) throw InvalidStateException(LOCATION, "End of
iteration");
return mpPos;
}
in the header file. Sorry for wild goose run. Thanks for the prompt reply and I learned something new about the the core implementation of the iterator as well.

Problems with list iterator and - operator

Can anyone tell me why my list iterator hates .end()-1 ? I want to iterato to one before the list end, I was under the impression you could always do this !
Code on request
std::list<Hammer::shared_ptr<Hammer::Actor>> collisionActorsList;
std::list<Hammer::shared_ptr<Hammer::Actor>>::iterator _actorUpdateIter = collisionActorsList.begin();
while(_actorUpdateIter != (collisionActorsList.end()-1)) // ERROR HERE
{
// check against every other actor
std::list<Hammer::shared_ptr<Hammer::Actor>>::iterator _otherActorsUpdateIter = _actorUpdateIter+1; // ERROR HERE TOO
while(_otherActorsUpdateIter != collisionActorsList.end())
{// SOME STUFF }
}
std::list uses a bidirectional iterator, which doesn't support operator- or operator+. Use std::prev(collisionActorsList.end()) and std::next(_actorUpdateIter).
As pointed out below in the comments, you should be aware of whether your list is empty. If it is, these will fail to do what you want. There's a simple function for that: collisionActorsList.empty().

How to modify objects using an iterator? using <list>

So here's an example. The star's mLocation and mSpeed are a Vector3 custom type.
I'v tried:
Star &star = *iStar;
Star star = *iStar;
Using iStar-> directly doesn't work with my operators, not sure why.
So whats the proper way to do that?
void UniverseManager::ApplySpeedVector()
{
std::list <Star>::const_iterator iStar;
for (iStar = mStars.begin(); iStar != mStars.end(); ++iStar)
{
// how to I get a hold on the object the iterator is pointing to so I can modify its values
// i tried Star &star = *iStar; this is illegal
// tried just using the iStar->mLocation += iStar->mSpeed this also fails due to the operator not accepting the values not sure why
// tried other things as well, so what is the proper way to do this?
iStar->SetLocationData( iStar->mLocation += iStar->mSpeed);
}
}
std::list<Star>::const_iterator iStar;
You cannot modify the objects in a container via a const_iterator. If you want to modify the objects, you need to use an iterator (i.e., std::list<Star>::iterator).
As James told you, you should use a std::list<Star>::iterator so that you can modify the object by calling a method or accessing its member variables.
It would be something like this:
void UniverseManager::ApplySpeedVector()
{
std::list <Star>::iterator iStar;
for (iStar = mStars.begin(); iStar != mStars.end(); ++iStar)
{
iStar->SetLocationData(iStar->mLocation += iStar->mSpeed);
}
}
Nevertheless, if you want to improve your code you might prefer to have a getter for accessing the location and the speed:
void UniverseManager::ApplySpeedVector()
{
std::list <Star>::iterator iStar;
for (iStar = mStars.begin(); iStar != mStars.end(); ++iStar)
{
iStar->SetLocationData(iStar->GetLocationData() + iStar->GetSpeed());
}
}
In any case, you have to use a non-const iterator.