为什么UE4 Specs向It() lambda传递特定变量时会发生崩溃?
你猜测的「对象在被引用前就已析构释放」完全正确,问题核心是UE4 Specs测试框架的执行时序和C++ lambda变量生命周期不匹配导致的:
核心原理
UE4 Specs框架的运行分为两个完全独立的阶段:
- 注册阶段:运行
Define()方法时,会立即执行所有Describe传入的lambda,把所有It对应的测试用例回调注册到框架中,注册完成后所有Describe层的lambda会全部执行完毕,内部定义的局部变量全部出作用域析构。 - 执行阶段:所有测试用例注册完成后,框架才会依次执行每个
It对应的测试lambda。
第一种写法(引用捕获Matcher)崩溃原因
你在内部Describe的lambda里定义的Matcher是注册阶段的局部变量,注册阶段结束后就会被析构。等到执行阶段跑It的lambda时,你捕获的&Matcher已经是野引用,指向的内存是已经释放的垃圾数据,调用方法必然崩溃。
第二种写法(值捕获Matcher)崩溃+IDE报错原因
- IDE报错原因:C中默认lambda的
operator()是const修饰的,值捕获到lambda内部的变量都是只读的。你调用的CanApplyAbilityBoost()方法没有加const修饰,相当于对const对象调用非const成员函数,不符合C标准所以IDE标红,能编译是MSVC编译器的宽松检查导致的,本质是非法操作。 - 崩溃原因:即使通过编译,
Matcher内部大概率保存了RuleOptions的引用/指针,而RuleOptions是外层Describelambda的局部变量,注册阶段结束后也已经析构,所以哪怕你拷贝了Matcher,内部指向的RuleOptions也是野的,调用方法还是会崩溃。
第三种写法正常的原因
你把Matcher的构造放到了It的lambda内部,RuleOptions通过[=]值捕获到了It的lambda里,在执行阶段跑测试的时候,RuleOptions和Matcher都是当前lambda的局部变量,生命周期完全匹配测试执行的周期,所以不会有野指针问题。
优化建议
如果不想每个It都重复写Matcher构造逻辑,可以用Specs框架自带的BeforeEach()减少重复代码:
Describe(TEXT("CanApplyAbilityBoost()"), [=, this]() { // 仅声明变量,注册阶段不会构造 FPF2AbilityBoostRuleOptionMatcher Matcher; BeforeEach([=, this, &Matcher]() { // 每个It执行前都会触发BeforeEach,此时构造的Matcher生命周期和测试用例匹配 Matcher = FPF2AbilityBoostRuleOptionMatcher(RuleOptions); }); It(TEXT("returns `false` for all abilities"), [=, this, &Matcher]() { for (const auto& AbilityScoreType : TEnumRange<EPF2CharacterAbilityScoreType>()) { TestFalse( FString::Format(TEXT("CanApplyAbilityBoost({0})"), { PF2EnumUtils::ToString(AbilityScoreType)}), Matcher.CanApplyAbilityBoost(AbilityScoreType) ); } }); });
内容的提问来源于stack exchange,提问作者GuyPaddock
相关产品推荐
相关产品推荐

