Reputation: 425
I am using Visual Studio 2012 C++ and I want to set two vectors with unique pointers equal to one another.
using namespace std;
vector<unique_ptr<Unit>> unitVector;
vector<unique_ptr<Unit>> nonDeadUnits;
.... (stuff added to unitVector) ....
for (auto unit = unitVector.begin(); unit != unitVector.end(); ++unit) {
if ((*unit)->health > 0) {
nonDeadUnits.push_back(*unit);
}
}
unitVector.clear();
unitVector = nonDeadUnits; // error here (see below error code)
I want to remove all of the units that have health less than 0, but if I try and directly remove them from the vector, I attempt to access memory that I shouldn't, killing the program. That is why I have opted to do it this way. The only problem is that unique_ptr does not allow the type of copying that I want. Here is the error:
error C2248: 'std::unique_ptr<_Ty>::operator =' : cannot access private member declared in class 'std::unique_ptr<_Ty>' c:\program files (x86)\microsoft visual studio 11.0\vc\include\xutility 2089
I would like to have unique_ptr's because the vectors call sub-class methods within a for loop later on, and it helps with overriding. So how do I set the vectors equal to one another or is there a better way?
Upvotes: 3
Views: 886
Reputation: 275385
The fast way to do this is with remove_if
and erase
, but that idiom violates DRY (don't repeat yourself) and I have seen people make subtle mistakes when using it (forgetting the 2nd iterator to erase
passing (inadequate) test cases, then failing in production!)
My solution to this kind of problem -- filtering a std::vector
for some property -- is to write a container-based algorithm to do it for me.
template<typename SeqContainer, typename Lambda>
SeqContainer&& remove_erase_if( SeqContainer&& c, Lambda&& test ) {
using std::begin; using std::end;
auto new_end = std::remove_if( begin(c), end(c), std::forward<Lambda>(test) );
c.erase( new_end, end(c) );
return std::forward<SeqContainer>(c);
}
now that you have a container based remove_erase_if
, we can filter the list:
// const & is important, we don't want to copy a `unique_ptr`
remove_erase_if( unitVector, [&]( std::unique_ptr<Unit> const& unit ) {
return (unit->health() <= 0);
});
... and that is it. Everything with health() <= 0
is removed from the std::vector
.
Other useful container based algorithms I find I use quite often include remove_erase
and sort_unique_erase
and binary_search
. Amusingly, while the above code works std::vector
, std::list
and std::deque
, I almost always use std::vector
: but writing it to work with any sequential container is easier than writing it to work with a std::vector
.
Another option for the design of these container algorithms is to take the container by value, and return it by value. This forces some std::move
spam, but is basically equally efficient at runtime.
Upvotes: 1
Reputation: 27125
The general idea is to use std::remove_if
to swap elements within unitsVector
, and then once all the dead units are at the end of the vector, you just chop them off.
#include <memory>
#include <vector>
struct Unit {
int health;
};
// The non-working version.
//
// void remove_dead_units(std::vector<std::unique_ptr<Unit>> &unitVector)
// {
// std::vector<std::unique_ptr<Unit>> nonDeadUnits;
// for (auto unit : unitVector)
// if (unit->health > 0)
// nonDeadUnits.push_back(unit);
// unitVector = nonDeadUnits;
// }
void remove_dead_units(std::vector<std::unique_ptr<Unit>> &unitVector)
{
auto isDead = [](const std::unique_ptr<Unit> &u) -> bool { return (u->health <= 0); };
auto newEnd = std::remove_if(unitVector.begin(), unitVector.end(), isDead);
unitVector.erase(newEnd, unitVector.end());
}
I'm sure there are other ways to do it, hewing more closely to what you tried (EDIT: in fact KerrekSB just posted one, using only a single std::move
and a swap
); but I think the "shuffle and chop" method is more modern-C++ish.
Upvotes: 8
Reputation: 477010
Perhaps the following logic would be simpler:
vector<unique_ptr<Unit>> unitVector = /* ... */;
vector<unique_ptr<Unit>> nonDeadUnits;
for (auto & p : unitvector)
{
if (p->health > 0) { nonDeadUnits.push_back(std::move(p)); }
}
unitVector.swap(nonDeadUnits);
Otherwise, the standard remove-erase idiom is probably more main-stream:
unitVector.erase(remove_if(unitVector.begin(), unitVector.end(),
[](unique_ptr<Unit> const & p) -> bool { return p->health <= 0; }),
unitVector.end());
Upvotes: 5