3

我正在使用 Visual Studio 2012 C++,我想设置两个具有唯一指针的向量彼此相等。

    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)

我想删除所有生命值小于 0 的单元,但是如果我尝试直接从向量中删除它们,我会尝试访问我不应该访问的内存,从而终止程序。这就是为什么我选择这样做的原因。唯一的问题是 unique_ptr 不允许我想要的复制类型。这是错误:

    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

我想要 unique_ptr,因为向量稍后会在 for 循环中调用子类方法,它有助于覆盖。那么如何将向量设置为彼此相等还是有更好的方法?

4

3 回答 3

8

一般的想法是用来std::remove_if交换 内的元素unitsVector,然后一旦所有的死单元都在向量的末尾,你就把它们砍掉。

#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());
}

我敢肯定还有其他方法可以做到这一点,更接近于你所尝试的(编辑:事实上,KerrekSB 刚刚发布了一个,只使用了一个std::move和一个swap);但我认为“洗牌和剁碎”方法更现代-C++ish。

于 2013-06-19T23:20:38.033 回答
5

也许下面的逻辑会更简单:

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);

否则,标准的 remove-erase 习惯用法可能更主流:

unitVector.erase(remove_if(unitVector.begin(), unitVector.end(),
                           [](unique_ptr<Unit> const & p) -> bool { return p->health <= 0; }),
                 unitVector.end());
于 2013-06-19T23:23:14.063 回答
1

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.

于 2013-06-20T00:41:04.877 回答