Почему спецификации UE4 отказывают, когда определенные переменные передаются в лямбду » It ()»?

#c #unreal-engine4

Вопрос:

При написании спецификаций UE4 я нахожу, что иногда значение, переданное в лямбду, вызывает сбой или непредсказуемый результат, в то время как в других случаях это не так.

Например, вызов любого метода для Matcher объекта внутри It() в приведенном ниже примере приведет к сбою (все значения являются данными мусора, когда я проверяю их в отладчике Rider).:

 void FPF2AbilityBoostRuleOptionMatcherSpec::Define()
{
    Describe(TEXT("when there are no rule options"), [=, this]()
    {
        const TArray<FPF2AbilityBoostRuleOption> RuleOptions = {};

        Describe(TEXT("CanApplyAbilityBoost()"), [=, this]()
        {
            FPF2AbilityBoostRuleOptionMatcher Matcher(RuleOptions);

            It(TEXT("returns `false` for all abilities"), [=, this, amp;Matcher]()
            {
                for (const autoamp; AbilityScoreType : TEnumRange<EPF2CharacterAbilityScoreType>())
                {
                    TestFalse(
                        FString::Format(TEXT("CanApplyAbilityBoost({0})"), { PF2EnumUtils::ToString(AbilityScoreType)}),
                        Matcher.CanApplyAbilityBoost(AbilityScoreType)
                    );
                }
            });
        });
        // ...
    });
    // ...
}
 

Переключение его на передачу по значению приводит к ошибке IDE, но он компилируется и выходит из строя, то же самое было ссылкой:

 void FPF2AbilityBoostRuleOptionMatcherSpec::Define()
{
    Describe(TEXT("when there are no rule options"), [=, this]()
    {
        const TArray<FPF2AbilityBoostRuleOption> RuleOptions = {};

        Describe(TEXT("CanApplyAbilityBoost()"), [=, this]()
        {
            FPF2AbilityBoostRuleOptionMatcher Matcher(RuleOptions);

            It(TEXT("returns `false` for all abilities"), [=, this, Matcher]()
            {
                for (const autoamp; AbilityScoreType : TEnumRange<EPF2CharacterAbilityScoreType>())
                {
                    TestFalse(
                        FString::Format(TEXT("CanApplyAbilityBoost({0})"), { PF2EnumUtils::ToString(AbilityScoreType)}),
                        // Next line gets highlighted in red in Rider but compiles. Error is:
                        // Cannot convert 'this' argument from type 'FPF2AbilityBoostRuleOptionMatcher const' to type
                        // 'FPF2AbilityBoostRuleOptionMatcher': function is missing 'const' qualifier`
                        Matcher.CanApplyAbilityBoost(AbilityScoreType)
                    );
                }
            });
        });
        // ...
    });
    // ...
}
 

Это работает, хотя (но Matcher это должно быть построено в каждом тестовом случае):

 void FPF2AbilityBoostRuleOptionMatcherSpec::Define()
{
    Describe(TEXT("when there are no rule options"), [=, this]()
    {
        const TArray<FPF2AbilityBoostRuleOption> RuleOptions = {};

        Describe(TEXT("CanApplyAbilityBoost()"), [=, this]()
        {
            It(TEXT("returns `false` for all abilities"), [=, this]()
            {
                FPF2AbilityBoostRuleOptionMatcher Matcher(RuleOptions);

                for (const autoamp; AbilityScoreType : TEnumRange<EPF2CharacterAbilityScoreType>())
                {
                    TestFalse(
                        FString::Format(TEXT("CanApplyAbilityBoost({0})"), { PF2EnumUtils::ToString(AbilityScoreType)}),
                        Matcher.CanApplyAbilityBoost(AbilityScoreType)
                    );
                }
            });
            });
        });
        // ...
    });
    // ...
}
 

Почему? Вызывается ли деструктор для этого объекта по какой-то причине до того, как на него ссылаются?

Комментарии:

1. Адресовано, @JaMiT. Немного доброты может пойти дальше 🙂

2. На самом деле здесь нет ничего, что выглядело бы особенно ужасно. Есть ли у вас в файле оптимизация? (Может объяснить, почему Matcher выглядит так, как будто он заполнен мусором). Это может быть что-то отрывочное, происходящее внутри одного из конструкторов Matcher или даже деструктора. Это также может быть частично объяснено, если Descirbe позволяет вызывать переданную функцию позже в строке, как только сопоставитель вышел за пределы области действия. Хотя это все еще не объясняет, почему ваш второй пример выходит из строя.

3. Проверьте документацию для It() . Ваши симптомы хорошо соответствуют документации, в которой говорится, что второй параметр (ваша лямбда) может быть вызван после завершения выполнения It() ; что второй параметр будет сохранен где-то для выполнения позже. Это соответствовало бы своего рода «ленивому» определению, в котором механизм записывает, как создавать спецификации, но генерирует каждый уровень детализации только по мере необходимости.

4. Спасибо, @JaMiT! Такое ощущение, что переменные во внешней области выделяются стеком, а затем, когда они используются, они указывают на мусорную память. Я посмотрю, смогу ли я копнуть глубже, основываясь на том, что вы здесь указали.