在执行std::函数析构函数时调用它



我想动态更改类的方法的行为,所以我实现了这些方法,调用std::function运算符(),该方法持有一个lambda函数的副本,该函数一次依赖于一些只有在类构造后才知道的值。Lambda会更改类的状态,因此它们会重置一个包含所有动态方法行为的容器
执行上述想法时,重置容器后,我无法访问lamba的捕获列表
以下片段再现了问题:

std::vector< std::function<void(std::string)> > vector;
int main() {
//Change class state when variable value will be known
std::string variableValue = "hello";
auto function = [variableValue](std::string arg) {
std::cout <<"From capture list, before: "<< variableValue << std::endl;
std::cout <<"From arg,          before: " << arg << std::endl;
vector.clear();
std::cout << "From capture list, after: " << variableValue << std::endl;
std::cout << "From arg,          after: " << arg << std::endl;
};
vector.push_back(function);
//Dynamic method execution
vector[0](variableValue);
return 0;
}

生产输出:

From capture list, before: hello
From arg,          before: hello
From capture list, after:
From arg,          after: hello

其中variableValue向量被清除后无效。

捕获列表无效是预期结果吗?在调用std::function析构函数后,使用任何其他局部变量(不仅仅是在捕获列表中)是否安全?是否有一种建议的方法/模式可以以更安全的方式实现相同的行为(不包括巨大的开关/如果处于类状态)?

对于这个问题,我们可以去掉std::function、lambda和向量。由于lambdas只是具有函数调用运算符的类的语法糖,因此您的测试用例实际上与以下内容相同:

struct Foo
{
std::string variableValue = "hello";
void bar(std::string arg)
{
std::cout <<"From capture list, before: "<< variableValue << std::endl;
std::cout <<"From arg,          before: " << arg << std::endl;
delete this;  // ugrh
std::cout << "From capture list, after: " << variableValue << std::endl;
std::cout << "From arg,          after: " << arg << std::endl;
}
};

int main()
{
Foo* ptr = new Foo();
ptr->bar(variableValue);
}

函数参数很好,因为它是一个副本,但在delete this之后,成员Foo::variableValue不再存在,因此您的程序在尝试使用它时有未定义的行为。

常见的观点是,继续运行函数本身是合法的(因为函数定义不是对象,不能被"删除";它们只是程序的一个基本属性),只要你把封装类的成员单独留下就足够了。

然而,我建议除非你真的需要,否则不要使用这种模式。这很容易让人们混淆你的类的所有权责任(即使"你的类"是从lambda表达式自动生成的!)。


捕获列表无效是预期结果吗?

是。

在调用std::function析构函数后,使用任何其他局部变量(不仅仅是在捕获列表中)是否安全?

是。

是否有一种建议的方法/模式可以以更安全的方式实现相同的行为(不包括巨大的开关/如果处于类状态)?

如果不了解你想做什么,就不可能确定。但你可以试着把shared_ptr存储在你的向量中,而不是…只是要小心不要在lambda本身中捕获shared_ptr,否则它将永远不会被清理!相反,捕获weak_ptr可能有助于此;它可以在λ体内"转换"为shared_ptr,这将在所述λ体内保护λ的寿命。

std::function的析构函数会破坏对象的目标(如果对象不是空的),其中对象是包装的可调用对象。

在您的情况下,目标是lambda表达式。当您使用lambda表达式时,编译器会生成一个"非并集非聚合类类型",该类型包含按值捕获的数据成员,并将operator()作为成员函数。

当您执行vector.clear()时,它的元素的析构函数会运行,因此闭包的值捕获析构函数(即成员变量)也会运行。

至于引用捕获,"引用变量的生存期在闭包对象的生存期结束时结束。">

因此,在std::function的析构函数运行后,访问任何捕获(无论是通过值还是通过引用)都是不安全的。

实际的operator()呢?"函数不是对象",所以它们没有生存期。因此,只要您不访问任何捕获,那么在析构函数运行后仅执行operator()应该是可以的。查看可以安全delete this的条件。

最新更新