为什么GCC的std :: function不使用对值传递的参数的rvalue引用来在它们的内部委托之间传递它们?

Mar*_*son 15 c++ gcc libstdc++ c++11 std-function

首先,请考虑以下代码:

#include <iostream>
#include <functional>

struct Noisy
{
  Noisy() { std::cout << "Noisy()" << std::endl; }
  Noisy(const Noisy&) { std::cout << "Noisy(const Noisy&)" << std::endl; }
  Noisy(Noisy&&) { std::cout << "Noisy(Noisy&&)" << std::endl; }
  ~Noisy() { std::cout << "~Noisy()" << std::endl; }
};

void foo(Noisy n)
{
  std::cout << "foo(Noisy)" << std::endl;
}

int main()
{
  Noisy n;
  std::function<void(Noisy)> f = foo;
  f(n);
}
Run Code Online (Sandbox Code Playgroud)

以及它在不同编译器中的输出:

Visual C++ (现场直播)

Noisy()
Noisy(const Noisy&)
Noisy(Noisy&&)
foo(Noisy)
~Noisy()
~Noisy()
~Noisy()
Run Code Online (Sandbox Code Playgroud)

Clang(libc ++)(见现场直播)

Noisy()
Noisy(const Noisy&)
Noisy(Noisy&&)
foo(Noisy)
~Noisy()
~Noisy()
~Noisy()
Run Code Online (Sandbox Code Playgroud)

GCC 4.9.0 (现场直播)

Noisy()
Noisy(const Noisy&)
Noisy(Noisy&&)
Noisy(Noisy&&)
foo(Noisy)
~Noisy()
~Noisy()
~Noisy()
~Noisy()
Run Code Online (Sandbox Code Playgroud)

也就是说,与Visual C++(和Clang + libc ++)相比,GCC再执行一次移动/复制操作,我们同意,在所有情况下(例如std::array<double, 1000>参数)效率都不高.

据我所知,std::function需要对一个包含实际函数对象的内部包装器进行虚拟调用(在我的例子中foo).因此,使用转发引用完美转发是不可能的(因为虚拟成员函数不能被模板化).

但是,我可以想象实现可以在std::forward内部所有参数,无论它们是通过值传递还是通过引用传递,如下所示:

// interface for callable objects with given signature
template <class Ret, class... Args>
struct function_impl<Ret(Args...)> {
    virtual Ret call(Args&&... args) = 0; // rvalues or collaped lvalues
};

// clever function container
template <class Ret, class... Args>
struct function<Ret(Args...)> {
    // ...
    Ret operator()(Args... args) { // by value, like in the signature
        return impl->call(std::forward<Args>(args)...); // but forward them, why not?
    }

    function_impl<Ret(Args...)>* impl;
};

// wrapper for raw function pointers
template <class Ret, class... Args>
struct function_wrapper<Ret(Args...)> : function_impl<Ret(Args...)> {
    // ...
    Ret (*f)(Args...);

    virtual Ret call(Args&&... args) override { // see && next to Args!
        return f(std::forward<Args>(args)...);
    }
};
Run Code Online (Sandbox Code Playgroud)

因为按值传递的参数只会变成右值引用(很好,为什么不呢?),右值引用将崩溃并保持右值引用,以及左值引用将崩溃并保持左值引用(请参阅此提案实时).这避免了任何数量的内部助手/代表之间的复制/移动.

所以我的问题是,为什么GCC对值传递的参数执行额外的复制/移动操作,而Visual C++(或Clang + libc ++)则没有(因为它似乎没有必要)?我希望STL的设计/实现具有最佳性能.

请注意,在std::function签名中使用rvalue引用,std::function<void(Noisy&&)>对我来说不是一个解决方案.


请注意,我不是要求解决方法.我认为可能的解决方法都不正确.

a)使用const左值参考!

为什么不?因为现在当我f用rvalue 调用时:

std::function<void(const Noisy&)> f = foo;
f(Noisy{});
Run Code Online (Sandbox Code Playgroud)

它抑制移动操作Noisy临时和力量副本.

b)然后使用非const rvalue引用!

为什么不?因为现在我f用左值调用时:

Noisy n;
std::function<void(Noisy&&)> f = foo;
f(n);
Run Code Online (Sandbox Code Playgroud)

它根本不编译.

Mar*_*sse 5

在libstdc ++中,std::function::operator()不直接调用该函数,它将该任务委托给一个帮助器_M_invoker.这种额外的间接水平解释了额外的副本.我没有研究代码,所以我不知道这个助手是否仅仅是方便,还是它扮演了重要的角色.无论如何,我相信要走的路是在gcc的bugzilla中提交增强PR .