std::remove_if会调用析构函数吗?

6

我正在使用 std::remove_if 函数将一个std::vector中的一些元素移动到末尾,以便我可以将它们复制到另一个向量中,然后删除该范围:

using ElemPtr = std::shared_ptr<Elem>;
auto iter = std::remove_if(source.begin(),source.end(), [&](const ElemPtr& e){ /* ... */ });
dest.insert(dest.end(),iter,source.end());
source.erase(iter,source.end());

发生的情况是,在调用std::remove_if后,要删除的值会调用其析构函数(即,它们被设置为null)。我最终只复制了一堆空指针,这并不好。
像cppreference.com这样的资源没有提到这种行为,所以我想知道这是否是编译器的错误?
我正在使用gcc 5.2.0。

看起来你可能想要使用 std::partitionstable_partition - Zan Lynx
@ZanLynx - 在指针上调用erase并不会删除指向的对象;它调用了析构函数,但对于指针来说,这并没有什么作用。然而,在这里的元素是std::shared_ptr,因此根据是否有其他指向该对象的shared_ptrweak_ptr对象,erase可能会最终删除该对象。 - Pete Becker
3个回答

19

std::remove_if不会将需要移除的元素移到末尾,而是将不需要移除的元素移到前面;其尾部的内容则未指定。

在此过程中,谓词所说需要被移除的一些元素可能被(准确地说,赋值于)其他元素覆盖。对于一个shared_ptr类型的元素,这意味着它很可能会销毁底层对象。

您似乎正在寻找的是std::partition。它的行为方式与您期望的std::remove_if的行为完全相同。


4
这不是一个bug。实际上,它们并没有被析构函数调用,而是被移动了。直到容器的erase方法被调用,它们的析构函数才会被调用。 cppreference.com提到:

通过移位(使用移动赋值)来删除元素,使得不需要删除的元素出现在范围的开头。剩余元素的相对顺序保持不变,容器的物理大小也不变。指向新逻辑结尾和物理结尾之间元素的迭代器仍然可以解引用,但元素本身具有未指定的值(根据MoveAssignable后置条件)。通常在调用remove后会调用容器的erase方法,该方法会删除未指定的值,并将容器的物理大小减小到与其新的逻辑大小匹配。


1

std::remove_if 移动分配迭代器值,这些值不满足给定的一元谓词(如果它们最初出现在至少一个满足给定一元谓词的迭代器值之后)。不会调用任何析构函数。

std::remove_if 的可能实现

template< typename ForwardIt, typename UnaryPredicate >
ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p) {
    first = std::find_if(first, last, p);
    if (first != last)
        for (ForwardIt i = first; ++i != last;)
            if (!p(*i))
                *first++ = std::move(*i); // Move assignment, no destructors called
    return first;
}

来源: cppreference.com

说明 (Erase–remove惯用语)

代码 [Wandbox]:

#include <algorithm>
#include <iostream>
#include <vector>

struct A {
    constexpr A(int a) noexcept : m_a(a) {}
    constexpr A(const A&) noexcept = default;
    constexpr A(A&&) noexcept = default;
    ~A() noexcept {
        std::cout << "Destruction: " << m_a << std::endl;
    }
    A& operator=(const A& a) noexcept {
        std::cout << "Copy assignment: " << a.m_a << std::endl;
        m_a = a.m_a;
        return *this;
    }
    A& operator=(A&& a) noexcept {
        std::cout << "Move assignment: " << a.m_a << std::endl;
        m_a = a.m_a;
        return *this;
    }

    int m_a;
};


int main() {
    std::vector< A > v = { 1, 2, 5, 4, 5, 5 };
    std::cout << "BEGIN REMOVE" << std::endl;
    auto end = std::remove_if(v.begin(), v.end(), [](const A& a) noexcept { return 5 == a.m_a; });
    std::cout << "BEGIN ERASE" << std::endl;
    v.erase(end, v.end());
    std::cout << "END" << std::endl;
    return 0;
}

Output:

Destruction: 5
Destruction: 5
Destruction: 4
Destruction: 5
Destruction: 2
Destruction: 1
BEGIN REMOVE
Move assignment: 4
BEGIN ERASE
Destruction: 4 // Could be garbage as well (unspecified, but valid, state)
Destruction: 5
Destruction: 5
END
Destruction: 1
Destruction: 2
Destruction: 4

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