如何将两个vector<unique_ptr<...>>设置为相等?

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循环中,向量调用子类方法会更方便进行覆盖。那么我该如何让这些向量相互等同呢?或者有更好的方法吗?


3
我猜想std::remove_if可能对你有所帮助。我会尝试编写一个示例并将其发布为答案。 - Quuxplusone
unitVector = std::move(nonDeadUnits); 可以解决你的问题,但正如 Quuxplusone 所说,还有更好的解决方案。 - Mike Vine
3个回答

8

通常的做法是使用std::remove_ifunitsVector中交换元素,一旦所有已死亡单位都在向量的末尾,您只需将它们砍掉即可。

#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++的风格。


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

否则,标准的删除-擦除惯用语可能更为主流:
unitVector.erase(remove_if(unitVector.begin(), unitVector.end(),
                           [](unique_ptr<Unit> const & p) -> bool { return p->health <= 0; }),
                 unitVector.end());

为什么使用 swap 而不是 move - Yakk - Adam Nevraumont

1
快速解决这个问题的方法是使用remove_iferase,但这种惯用法违反了DRY(不要重复自己)原则,我曾看到人们在使用它时犯下微妙的错误(忘记将第二个迭代器传递给erase,并且测试案例不足,然后在生产环境中失败!)
我解决这种问题的方法是编写一个基于容器的算法来为我过滤std::vector的某些属性。
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);
}

现在您有一个基于容器的remove_erase_if,我们可以对列表进行过滤:

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

...就是这样。从std::vector中删除所有health() <= 0的内容。

我经常使用的其他有用的基于容器的算法包括remove_erasesort_unique_erasebinary_search。有趣的是,虽然上面的代码适用于std::vectorstd::liststd::deque,但我几乎总是使用std::vector:但编写它使其适用于任何连续容器比编写它使其适用于std::vector更容易。

这些容器算法的另一个设计选项是按值获取容器,并按值返回。这会强制进行一些std::move操作,但在运行时基本上同样有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接