所以在现代 C++ 中,我们使用智能指针来确保共享资源在我们使用之前不会被删除。
lambda 回调是否有 std::function<> 的等价物? 在传递特定签名的函数而不是整个类或接口的地方,一般委托注入。
问题示例代码:
struct Foo{
int i = 0;
void addInt(int input){
i += input;
}
};
int main(int argc, char* args[]){
Foo* lostMemory = new Foo();
std::function<void(int)> lambda = [lostMemory](int b) { lostMemory->addInt(b); };
//run memory good
lambda(8);
//run memory deleted - should crash right?
delete lostMemory;
lambda(8);
return 0;
}
https://godbolt.org/z/qsK65ozGG
它没有崩溃的事实不是问题或问题
问题:像这样创建 std::functional 的正确方法是什么,资源可能会被删除?
注意:在一个示例中,lostMemory 将是传递自身的类的实例,并且不能仅更改为 std::shared_ptr<>。堆栈溢出简化使它看起来像是答案。
在下面回答了我如何处理它的问题。
NOT 使用 std::enable_shared_from_this() 因为它充满了危险的设置问题和正确使用的边缘情况
你为什么不像你提到的那样只使用智能指针?
#include <functional>
#include <memory>
struct Foo{
int i = 0;
void addInt(int input){
i += input;
}
};
int main(int argc, char* args[]){
std::shared_ptr<Foo> lostMemory( new Foo() );
std::function<void(int)> lambda = [lostMemory](int b) { lostMemory->addInt(b); };
//run memory good
lambda(8);
//run memory deleted - should crash right?
lostMemory.reset();
lambda(8);
return 0;
}
看,lambda 只是幕后的匿名函子。如果你将上面的代码粘贴到 cppinsights.io 上,你将得到类似这样的东西(稍微清理一下):
#include <functional>
#include <memory>
struct Foo
{
int i = 0;
inline void addInt(int input)
{
this->i = this->i + input;
}
};
int main(int argc, char ** args)
{
std::shared_ptr<Foo> lostMemory = std::shared_ptr<Foo>(new Foo());
class __lambda_13_39
{
public:
inline /*constexpr */ void operator()(int b) const
{
static_cast<const std::__shared_ptr_access<Foo, 2, false, false>&>(lostMemory).operator->()->addInt(b);
}
private:
std::shared_ptr<Foo> lostMemory;
public:
__lambda_13_39(const std::shared_ptr<Foo> & _lostMemory)
: lostMemory{_lostMemory}
{}
};
std::function<void (int)> lambda = std::function<void (int)>(__lambda_13_39{lostMemory});
lambda.operator()(8);
static_cast<std::__shared_ptr<Foo, 2>&>(lostMemory).reset();
lambda.operator()(8);
return 0;
}
你可以看到仿函数持有一个
std::shared_ptr<Foo>
作为成员。这正是 lambda 也会做的事情。
或者,对于更进化的情况,使用函子本身而不是 lambda。
所以在评论讨论之后我想出了一个适合我的解决方案。它绝对不完美,开发人员可能会错误地使用它,但它比我拥有的要好。
所以第 1 步:我没有传递原始函数,而是创建了一个结构,该结构将具有一个可以检查的 weak_ptr<>。我们需要将 this 绑定到拥有该函数的对象,但我们稍后会谈到。
template<typename _aliveToken, class _args>
class SharedFunction{
public:
SharedFunction(const std::weak_ptr<_aliveToken>& aliveToken, const std::function<void(_args)>& function):
m_aliveToken(aliveToken),
m_function(function)
{
}
std::weak_ptr<_aliveToken> m_aliveToken;
std::function<void(_args)> m_function;
};
在我的代码中,我将其链接到回调容器中,但为此我们将其保留在单个“安全”函数中,既美观又简单。模板只是阻止我们绑定到单个指针类型或输入值。
首先我们必须在我们的 foo 类中创建一个锁对象。我们不能让 foo 的实例有一个指向自身的智能指针,但它可以包含一个在它被删除时消失的智能指针
struct Foo {
std::shared_ptr<char> lock;
int i = 0;
Foo() : lock(std::make_shared<char>(0)){
}
void addInt(int input) {
i += input;
}
};
接下来我们需要检查我们新的“安全”功能的功能,以确保我们仍然可以使用它
void checkObjectFirst(SharedFunction<char, int>& function, int value)
{
const auto isSafe = function.m_aliveToken.lock();
if(isSafe){
function.m_function(value);
}
}
现在我们的代码知道一旦所有者不再活着就不再调用那个 lambda
int main(int argc, char* args[])
{
Foo* lostMemory = new Foo();
auto lambda = [lostMemory](int b) { lostMemory->addInt(b); };
SharedFunction<char, int> function(lostMemory->lock, lambda);
//run memory good
checkObjectFirst(function, 8);
//run memory deleted - we're now good!
delete lostMemory;
checkObjectFirst(function, 8);
return 0;
}