当存储在容器中时,std :: function到底能做什么?

问题描述

std::function构造函数存储在容器中的确切作用是什么?

在此测试代码中:

struct A {
    int sn;
    A() = delete;
    A(int v) : sn(v) { cout << "A::init(" << sn << ')' << endl; }
    A(const A& a) : sn(a.sn+1) { cout << "A::copy(" << sn << ')' << endl; }
    A(A&& a) : sn(a.sn+1) { cout << "A::move(" << sn << ')' << endl; }
    ~A() { cout << "A::delete(" << sn << ')' << endl; }
};

void func(int a,A &b) {
    cout << "func2:" << a << ',' << b.sn << endl;
}

int main(int argc,char *argv[]) {
    std::vector<std::function<void(void)>> fv;
    A a(1);
    cout << "call bind()" << endl;
    fv.emplace_back(std::bind(func,1,a));
    fv.front()();
    cout << "end of local scope" << endl;
}

我声明class A的move和copy构造函数以累积序列号,以便可以将其作为第N个创建的实例进行跟踪。结果是:

A::init(1)
call bind()
A::copy(2)
A::move(3)
A::move(4)
A::delete(3)
A::delete(2)
func2:1,4
end of local scope
A::delete(1)
A::delete(4)

首先在a(1)本地范围内创建实例main(),然后在调用a(2)时将其复制为新实例std::bind(),然后再次移入{{1} }动态分配的内存(或内部内存片段?)来保存std::function实例的副本,因此我们拥有std::bind()实例保存的A的第三实例。

到目前为止,这是可以理解的,但是为什么还有另一种动作构造?以及为什么第三个实例在第二个实例之前被销毁(应该在std::function返回时清除?)?

如果我将std::bind()函数重写为:

main()

那么结果将是:

int main(int argc,char *argv[]) {
    A a(1);
    cout << "call bind()" << endl;
    std::function<void(void)> f(std::bind(func,a));
    f();
    cout << "end of local scope" << endl;
}

没有第二步建设,一切看起来都合理。

在这种情况下,调用A::init(1) call bind() A::copy(2) A::move(3) A::delete(2) func2:1,3 end of local scope A::delete(3) A::delete(1) 创建emplace_back()实例时到底发生了什么?

解决方法

首先在a(1)本地范围内创建实例main(),然后在调用a(2)时将其复制为新实例std::bind(),然后再次移入{{1} }动态分配的内存

您错过了一步:std::function的构造函数采用了按值计算

std::function

template<typename F> std::function<void(void)>::function(F f); 正在转发包含emplace_back作为右值的std::bind包装器,是的,但是此构造方法仅按值而不是右值引用接受,因此a(2)被移动并变为a(2)只是为了初始化构造函数的参数。 然后构造函数将该值移出到动态存储中。

a(3)

简化的区别在于您没有将int main() { A a(1); // A::init(1) // this is the temporary that gets materialized in main's scope for the call to emplace_back auto wrapper = std::bind(func,1,a); // A::copy(2); // emplace_back receives (a reference to) the temporary as an rvalue,and so it passes std::function's constructor a reference to the same object,also as an rvalue // but std::function's constructor only takes values... std::function<void(void)> f(std::move(wrapper)); // initializing the parameter of constructor (in main/emplace_back's context): A::move(3) // constructor then initializes dynamically allocated object: A::move(4) // constructor destroys parameter object: A::delete(3) // f gets destroyed,takes dynamically allocated object with it: A::delete(4) // wrapper (the temporary) gets destroyed (originally at the ; after emplace_back): A::delete(2) // a gets destroyed: A::delete(1); } // Note: the reason A::delete(4) comes before A::delete(1) in my version but not yours is that your A(4) lives inside fv,which is created at the top of main and thus is the last thing destroyed,but mine lives inside f,which is created at the end and is the first thing destroyed 的结果作为临时实例来实现。基本上,std::bind要求参数已被完全构造(与任何函数调用一样)。因此,您只需要执行emplace_back(及其内部副本)即可调用std::bind,然后 then emplace_back将该对象移至构造函数参数。但是,当您将emplace_back prvalue直接传递给std::bind构造函数时,构造函数参数对象是通过在执行过程中将std::function的结果对象设置为参数对象而直接构造的。>

另一种说法:std::bind的构造函数必须使用新创建的对象来调用。使用std::function时,中间函数“忘记”了emplace_back调用是一个新构造的对象,因此通过执行“外部”移动来构造一个新的对象。当您删除std::bind时,不会忘记emplace_back对象是新构造的事实,并且移动也被忽略了。

另一种说法:std::bind声称的“完美”转发实际上并不完美;这样的事情是不可能的(必须修改用户代码)。 “完美”转发将左值作为左值转发,但同时将x值和pr值折叠为x值(通过要求prvalue在调用该函数之前已经实现)。用xvalue调用emplace_back的构造函数会导致两次移动,而使用prvalue调用会导致一次移动。您的第二个版本将prvalue传递给构造函数,而第二个版本将xvalue传递给构造函数,所以它们具有不同的行为。

您可以使用包装函子获得所需的结果:

std::function

这会将对template<typename F> struct initializer { F f; operator decltype(f())() && { return std::move(f)(); } }; int main(int argc,char *argv[]) { std::vector<std::function<void(void)>> fv; A a(1); cout << "call bind()" << endl; fv.emplace_back( initializer{[&]() -> std::function<void(void)> { return std::bind(func,a); }}); fv.front()(); cout << "end of local scope" << endl; } 的调用直接放在对std::bind的构造函数(位于std::function中)的调用之下,因此prvalue可以直接初始化构造函数参数,而无需进行任何移动。 return也直接在向量的“空”空间中构造,没有进一步的移动(毕竟std::function就是这样做的。)