Is this a good or standard practice to code like this to loop through a vector while deleting unwanted elements from it without losing performance. If there is a faster way please suggest it.
This vector is of the form std::vector<AnimationState*> activeAnimations;
void AnimationPack::removeDeadAnimations()
{
int counter = 0;
std::remove_if(activeAnimations.begin(), activeAnimations.end(),
[&](AnimationState*& animation) {
if (animation->isActive())
{
counter++;
return true;
}
else
return false;
});
activeAnimations.erase(activeAnimations.end() - counter, activeAnimations.end());
}
Edited version
void AnimationPack::removeDeadAnimations()
{
activeAnimations.erase(std::remove_if(activeAnimations.begin(), activeAnimations.end(),
[&](AnimationState*& animation) {
if (animation->isActive())
return true;
else
return false;
}),activeAnimations.end());
}
Edited Code (As suggested from comments)
void AnimationPack::removeDeadAnimations()
{
activeAnimations.erase(std::remove_if(activeAnimations.begin(), activeAnimations.end(),
[](AnimationState*& animation) { return animation->isActive(); }), activeAnimations.end());
}
Yes, it is called an erase-remove idiom.
Quote from Wikipedia:
The erase–remove idiom is a common C++ technique to eliminate elements
that fulfill a certain criterion from a C++ Standard Library
container.
erase can be used to delete an element from a collection, but for
containers which are based on an array, such as vector, all elements
after the deleted element have to be moved forward, to avoid "gaps" in
the collection.
The algorithm library provides the remove and remove_if algorithms
for this.
These algorithms do not remove elements from the container, but move
all elements that don't fit the remove criteria to the front of the
range, keeping the relative order of the elements. This is done in a
single pass through the data range.
remove returns an iterator pointing to the first of these elements, so
they can be deleted using a single call to erase.
Removes and delete the element from the vector while iterating through it.
void AnimationPack::removeDeadAnimations()
{
activeAnimations.erase(std::remove_if(activeAnimations.begin(), activeAnimations.end(),
[&](AnimationState*& animation) {
if (animation->isActive())
return false;
else
{
delete animation;
return true;
}
}), activeAnimations.end());
}
Related
given two vectors
std::vector<SomeStruct> items; //1'000'000 items
std::vector<int> selection; //900'000 unique indices in ascending order
where selection contains valid indices into items, how can I shrink items efficiently to only contain the elements that are initially indexed by selection?
I am going to write this answer in reverse. Bear with me, I hope you will understand.
Lets first write a wrapper that lets us iterate only selected items:
#include <iostream>
#include <vector>
struct SomeStruct {};
struct selected_item {
std::vector<SomeStruct>& items;
std::vector<size_t>& selection;
struct iterator {
std::vector<SomeStruct>& items;
std::vector<size_t>::iterator selection_iterator;
SomeStruct& operator *(){
return items[*selection_iterator];
}
iterator& operator++(){
++selection_iterator;
return *this;
}
bool operator!=(const iterator& other){
return selection_iterator != other.selection_iterator;
}
};
iterator begin() { return {items,selection.begin()}; }
iterator end() { return {items,selection.end()};}
};
int main() {
std::vector<SomeStruct> items{{},{},{},{}};
std::vector<size_t> selection{1,3};
for (auto& i : selected_item{items,selection}){
std::cout << "item selected\n";
}
}
Using that you can now write a loop that moves selected items from items into a new vector, then move that new vector into items:
int main() {
std::vector<SomeStruct> items{{},{},{},{}};
std::vector<size_t> selection{1,3};
std::vector<SomeStruct> temp_items;
temp_items.reserve(selection.size());
for (auto& i : selected_item{items,selection}){
temp_items.emplace_back(std::move(i));
}
items = std::move(temp_items);
}
Supposed SomeStruct can be moved, this will not copy any SomeStruct. However, also moving is not for free. Depending on why you actually want to remove elements from items (why not populate a vector of selected items in the first place, instead of populating a vector of indices?) you can also consider to skip the moving altogether and use only the above wrapper to do whatever you want to do with the selected items. As 90% of the items are selected, it might be that the savings in memory and more efficient element access (due to a smaller vector) does not outweigh the moving, so you might as well directly do:
int main() {
std::vector<SomeStruct> items{{},{},{},{}};
std::vector<size_t> selection{1,3};
for (auto& i : selected_item{items,selection}){
do_something_with_selected_item(i);
}
}
Another option would be to actually erase elements from items. I did not consider it because I expect it to be rather costly. I might be wrong about that. As always, to know what is more efficient you need to measure.
PS: The wrapper is tested with gcc. I find it a little annoying to write custom iterators, not sure if it needs eg an operator== or a post-increment. I only implemented what was necessary to make gcc happy.
I try to find optimal data structure for next simple task: class which keeps N last added item values in built-in container. If object obtain N+1 item it should be added at the end of the container and first item should be removed from it. It like a simple queue, but class should have a method GetAverage, and other methods which must have access to every item. Unfortunately, std::queue doesn't have methods begin and end for this purpose.
It's a part of simple class interface:
class StatItem final
{
static int ITEMS_LIMIT;
public:
StatItem() = default;
~StatItem() = default;
void Reset();
void Insert(int val);
int GetAverage() const;
private:
std::queue<int> _items;
};
And part of desired implementation:
void StatItem::Reset()
{
std::queue<int> empty;
std::swap(_items, empty);
}
void StatItem::Insert(int val)
{
_items.push(val);
if (_items.size() == ITEMS_LIMIT)
{
_items.pop();
}
}
int StatItem::GetAverage() const
{
const size_t itemCount{ _items.size() };
if (itemCount == 0) {
return 0;
}
const int sum = std::accumulate(_items.begin(), _items.end(), 0); // Error. std::queue doesn't have this methods
return sum / itemCount;
}
Any ideas?
I'm not sure about std::deque. Does it work effective and should I use it for this task or something different?
P.S.: ITEMS_LIMIT in my case about 100-500 items
The data structure you're looking for is a circular buffer. There is an implementation in the Boost library, however in this situation since it doesn't seem you need to remove items you can easily implement one using a std::vector or std::array.
You will need to keep track of the number of elements in the vector so far so that you can average correctly until you reach the element limit, and also the current insertion index which should just wrap when you reach that limit.
Using an array or vector will allow you to benefit from having a fixed element limit, as the elements will be stored in a single block of memory (good for fast memory access), and with both data structures you can make space for all elements you need on construction.
If you choose to use a std::vector, make sure to use the 'fill' constructor (http://www.cplusplus.com/reference/vector/vector/vector/), which will allow you to create the right number of elements from the beginning and avoid any extra allocations.
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
I cannot call a function that does a push_back into a vector
void GameState::InitialiseBullet(float x, float y, float vx, float vy)
{
Bullet* bullets = new Bullet();
bullets->SetSize(5.f, 20.f);
bullets->AddFrame("./images/bullet.png");
bullets->Play();
bullets->SetX(x);
bullets->SetY(y);
bullets->velocityX = vx;
bullets->velocityY = vy;
bullets->isActive = true;
gameObjects.push_back(bullets);
}
when it is inside the following for loop
for (auto& object : gameObjects)
{
//Determine the type at runtime
if (dynamic_cast<Player*>(object) != 0)
{
//Process player-specific logic
PlayerLogic(dynamic_cast<Player*>(object), a_fTimeStep);
}
//Determine the type at runtime
if (dynamic_cast<Bullet*>(object) != 0)
{
//Process bullet-specific logic
BulletLogic(dynamic_cast<Bullet*>(object), a_fTimeStep);
}
if (dynamic_cast<Enemy*>(object) != 0)
{
//Process enemy-specific logic
Enemy* enemy = dynamic_cast<Enemy*>(object);
EnemyLogic(enemy, lowerAliens);
if (enemy->GetIsActive() == true)
{
allDead = false;
}
}
//Update and draw our objects
object->Update(a_fTimeStep);
object->Draw();
}
The piece of code that calls the function:
if (createBullet == true)
{
InitialiseBullet(bulletX, bulletY, 0, 500);
createBullet = false;
}
That code works when outside the for loop. However, I need the for loop to provide access to each of my player, enemy and bullet objects. Is there a way to push_back to a vector inside a for loop that is based on the same vector? I get a "Expression: Vector iterators incompatible" error when it's inside the loop. Any ideas? New to C++ programming.
It looks like you are pushing into the same vector you are iterating, that means, you are forcing items realocation and iterator invalidation; in other words - your data moves to different location and used iterator becomes invalid.
I rarely see situation where you really need to iterate and append same vector, so take a look into your code again.
If you really need to do that, iterate this way:
for (size_t i = 0; i < gameObjects.size(); ++i)
{/*Some code*/}
Also using this method you should use gameObjects[i]. instead of it->
It's just a vector of pointers, so it's not very big.
The objects being added is probably even smaller.
You could make a copy of the vector and iterate over the copy while inserting into the real one.
You could put new items into a new, empty vector while you iterate, and then splice them onto the real one at the end.
To delete objects, you could do either of those things, or you could simply set a flag "isZombie" and then remove all the zombies at the end.
These aren't the only answers, but they all work.
When using iterators to loop through your vector you can't in this 'for-loop' modify the vector.
A quick google gave me this; which seemd to fit your case pretty well.
Probably because the push_back ... caused an internal
reallocation in the vector thus all its iterators were invalidated.
Source: http://www.cplusplus.com/forum/beginner/64854/
Do I understand you right when I'm assuming your using iterators due to your error message.
One question you should ask yourself is why you would ever want to add instances to this vector, maybe you should rethink your design slightly to avoid this.
Okay, I have a STL list of references I am iterating through. This function has three equivalent parts. The function takes a wstring as a parameter, and runs the appropriate if statement. I have reduced the code to one if statement to try and get it working.
So, I check to see what has been passed in as an argument. I then check to see if the ClassItem is a certain type of animal. If it is, I check if it is hungry, and erase it from the list. I am just trying to avoid seg faults right now, and cannot seem to do it.
list<ClassItem *>::iterator i = Items.begin();
while(i != Items.end())
{
if(!AnimalType.compare(L"tiger"))
{
if((*i)->IsAnimalType(L"tiger"))
{
if((*i)->IsHungry())
{
i = Items.erase(i);
}
}
else
{
++i;
}
}
// I have tried removing this
else
{
i++;
}
}
I was under the impression that the current iterator is invalidated when I call erase. So, if I erase an element, I return the next valid iterator. Where am I going wrong?
EDIT: Thank you for all the quick help. The problem has been fixed. I have made use phresnel's solution and it worked wonderfully.
You are better off by using std::list::remove_if with a suitable predicate. This avoids the manual loop entirely, reducing scope for errors, and helping to either remove or at least you localise the source of the problem, since you can rely on this idiom being correct as long as your predicate is.
bool badAnimal(ClassItem * item)
{
// return true if animal is to be removed
}
Items.remove_if(badAnimal);
I see no potential for a segfault here. Anyways:
There are (IMHO) two possible problems:
if(!AnimalType.compare(L"tiger"))
This looks fishy. What is AnimalType? Do you really expect the value of if(!AnimalType.compare(L"tiger")) to change during iteration, if AnimalType itself does not?
In any case, it looks like a read, therefore shouldn't write. It looks constant, therefore shouldn't change.
Then:
if((*i)->IsAnimalType(L"tiger"))
{
if((*i)->IsHungry())
{
i = Items.erase(i);
}
// NO ITERATION IN CASE OF NOT HUNGRY.
// ONCE TRAPPED HERE, YOU HAVE AN INFINITE LOOP,
// EXCEPT AnimalType.compare(L"tiger") DOES SOMETHING
// NON-SANE.
}
else
{
++i;
}
this should better be:
if((*i)->IsAnimalType(L"tiger") && (*i)->IsHungry())
{
i = Items.erase(i);
}
else
{
++i;
}
However, even better would be to use the standard algorithms for element removal.
you may want to add
continue;
after your erasion.