为什么std :: remove_if创建了这么多闭包?

LWi*_*sey 8 c++ stl c++14

在这个例子中,一个foo实例除了打印它是复制还是移动构造之外什么都不做.

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

struct foo {
    foo()=default;
    foo(foo &&) { std::cout << "move constructed\n"; }
    foo(const foo &) { std::cout << "copy constructed\n"; }
};

int main()
{
    foo x;
    std::vector<int> v; // empty

    std::remove_if(v.begin(), v.end(),
                   [x=std::move(x)](int i){ return false; });
}
Run Code Online (Sandbox Code Playgroud)

这会产生以下输出:

move constructed
copy constructed
move constructed
move constructed
copy constructed
copy constructed
Run Code Online (Sandbox Code Playgroud)

问题:

  • 为什么要std::remove_if创建这么多的闭包?
  • 即使需要多个中间实例,人们也会期望它们都是rvalues; 那么为什么有些人会复制?

编译器是gcc 8.1.1

dfr*_*fri 7

如果我们看一下gcc的libstdc ++ - v3 的实现,std::remove_if我们注意到谓词在到达最下面的__find_if函数(由...使用remove_if)之前向下传递调用链(通过值,有时)几步.

让我们算一下动作和副本:

  1. move constructed当谓词(包括捕获的x)通过值发送,但作为非左值发送到std::remove_if入口点时

  2. copy constructed当传递给__gnu_cxx::__ops::__pred_iter(...)函数时,反过来:

  3. 调用_GLIBCXX_MOVE,因此move constructed,

  4. 它将谓词移动到_Iter_predctor,后者将it(move constructed)移动到_M_pred成员中.

  5. 从电话std::remove_ifstd::__remove_if似乎被优化,因为_Iter_pred不是左值,我猜,但__remove_if反过来通过对包装的谓词,通过价值,std::__find_if为另一个copy constructed调用.

  6. std::__find_if反过来,将包装的谓词按值转发到另一个__find_if重载,最后是该调用链的接收器和最终的重载copy constructed.

它可以是有趣的与如铛的比较实现std::remove_if,如铛(6.0.1),不产生对OP的这一举动复制链std::remove_if的例子.快速浏览显示,似乎clang在谓词类型上使用traits以确保将谓词作为左值引用传递.

clang和gcc都为后面的设计示例生成相同的move/ copy链,这显示了与gcc实现类似的链:

#include <iostream>
#include <utility>

struct foo {
    foo() = default;
    foo(foo &&) { std::cout << "move constructed\n"; }
    foo(const foo &) { std::cout << "copy constructed\n"; }
};

template <typename Pred>
struct IterPred {
    Pred m_pred;
    explicit IterPred(Pred pred) : m_pred(std::move(pred)) {}
};

template <typename T>
inline IterPred<T> wrap_in_iterpred (T l) { 
    return IterPred<T>(std::move(l)); 
}

template <typename T>
void find_if_overload(T l) {
    (void)l;
}

template <typename T>
void find_if_entrypoint(T l) { 
    find_if_overload(l);
}

template <typename T>
void remove_if_entrypoint(T l) { 
    find_if_entrypoint(
        wrap_in_iterpred(l));
}

int main()
{
    foo x;
    remove_if_entrypoint([x=std::move(x)](int){ return false; });
}
Run Code Online (Sandbox Code Playgroud)

gcc(8.2.0)和clang(6.0.1)都产生以下链:

move constructed
copy constructed
move constructed
move constructed
copy constructed
Run Code Online (Sandbox Code Playgroud)