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

时间:2018-08-25 23:39:16

标签: c++ stl c++14

在此示例中,(Immediately Invoked Function Expression)实例不执行任何操作,仅打印其是复制构造还是移动构造。

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

问题:

  • 为什么move constructed copy constructed move constructed move constructed copy constructed copy constructed 创建这么多的闭包?
  • 即使需要多个中间实例,也可以期望它们都是右值;那么为什么其中一些是复制构造的?

编译器为gcc 8.1.1

1 个答案:

答案 0 :(得分:7)

如果我们看一下the implementation of std::remove_if in gcc's libstdc++-v3,则会注意到谓词在到达最低的__find_if函数(由{{ 1}})。

让我们计算一下移动和复制:

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

  2. x传递给__gnu_cxx::__ops::__pred_iter(...)函数时,该函数依次为:

  3. 调用_GLIBCXX_MOVE macro,因此调用copy constructed

  4. 将该谓词移至_Iter_pred ctor,该谓词(move constructed)移入move constructed成员。

  5. _M_predstd::__remove_if的调用似乎已经过优化,因为std::remove_if不是左值,但是_Iter_pred依次通过将包装的谓词按值划分为std::__find_if,以进行另一个__remove_if调用。

  6. copy constructed依次按值将包装的谓词转发到another __find_if overload,该谓词最终是该调用链的接收器,也是最终的std::__find_if。 / p>

与例如clang的实现copy constructed,因为clang(6.0.1)不会为OP的std::remove_if示例生成此移动副本链。快速浏览一下,似乎似乎clang use traits on the predicates type确保通过谓词as an lvalue reference

clang和gcc都为下面的人为示例生成相同的std::remove_if / move链,该链显示了与gcc的实现类似的链:

copy

在gcc(8.2.0)和clang(6.0.1)都产生以下链的情况下:

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