UE5.7.1 源码版 UFUNCTION 参数传递踩坑:为什么 BlueprintImplementableEvent(FString) 会编译失败?
2026/7/5 22:05:02 网站建设 项目流程

前言

最近在使用UE5.7.1 源码版开发 UMG Widget 时,遇到了一个比较奇怪的问题。

同样是UFUNCTION(BlueprintImplementableEvent),下面两个函数,一个可以正常编译,一个却直接报错。

UFUNCTION(BlueprintImplementableEvent) void SwitchBackgroundStation(int32 StationIndex);

正常编译。

而下面这个:

UFUNCTION(BlueprintImplementableEvent) void SetTips(FString Tip);

却报出了下面的错误:

ItemTip.gen.cpp(62): error C2511: void UItemTip::SetTips(const FString&) 'UItemTip' 中没有找到重载的成员函数

刚开始一直以为是:

  • Intermediate 缓存没有清理

  • UHT 没有重新生成

  • Live Coding 导致旧代码残留

  • BlueprintImplementableEvent 使用错误

结果全部排查后都不是。

最终通过查看 UHT 生成代码以及多组实验,终于定位到了真正原因。


一、问题复现

Widget 定义如下:

UCLASS() class STEPEDITOR_API UItemTip : public UUserWidget { GENERATED_BODY() public: UFUNCTION(BlueprintImplementableEvent) void SetTips(FString Tip); };

编译报错:

ItemTip.gen.cpp(62): error C2511 void UItemTip::SetTips(const FString& Tip) 'UItemTip' 中没有找到重载成员函数

注意这里有一个细节:

自己声明的是

void SetTips(FString Tip);

而 UHT 生成的是

void UItemTip::SetTips(const FString& Tip)

参数类型已经发生了变化。


二、查看 UHT 生成代码

打开:

Intermediate/Build/.../ItemTip.gen.cpp

可以看到:

void UItemTip::SetTips(const FString& Tip) { ItemTip_eventSetTips_Parms Parms; Parms.Tip = Tip; UFunction* Func = FindFunctionChecked(NAME_UItemTip_SetTips); ProcessEvent(Func, &Parms); }

这里已经明确可以看到:

FString

被 UHT 自动转换成了:

const FString&

这也是编译失败的直接原因。


三、进一步验证

为了确认是不是只有 FString 有问题,我又增加了几个测试函数。

UFUNCTION(BlueprintImplementableEvent) void TestString(FString Str); UFUNCTION(BlueprintImplementableEvent) void TestText(FText Text); UFUNCTION(BlueprintImplementableEvent) void TestName(FName Name); UFUNCTION(BlueprintImplementableEvent) void TestArray(TArray<int32> Array);

结果如下:

参数类型编译结果
int32✅ 正常
FString❌ C2511
FText❌ C2511
FName❌(同样需要 const 引用)
TArray❌ C2511

可以发现:

所有大型对象类型都会出现相同的问题。


四、为什么 int32 没问题?

继续测试:

UFUNCTION(BlueprintImplementableEvent) void SwitchBackgroundStation(int32 StationIndex);

完全正常。

原因很简单。

对于基础类型:

int32 float bool

UHT 不会修改参数类型。

生成代码仍然是:

void SwitchBackgroundStation(int32 StationIndex)

所以不会发生签名不一致。


五、BlueprintCallable 呢?

随后又测试了:

UFUNCTION(BlueprintCallable) void TestString(FString Str);

编译报错:

LNK2019 无法解析的外部符号 UItemTip::TestString(FString)

这个错误和前面的不是同一个问题

原因非常简单:

BlueprintCallable只是把函数暴露给 Blueprint。

它仍然是一个普通 C++ 函数。

因此必须提供 cpp 实现:

void UItemTip::TestString(FString Str) { }

否则一定会出现 LNK2019。

所以:

  • BlueprintCallable 的 LNK2019 属于正常行为;

  • BlueprintImplementableEvent 的 C2511 才是本文讨论的问题。


六、解决方案

把所有大型对象参数统一改成const 引用

例如:

UFUNCTION(BlueprintImplementableEvent) void SetTips(const FString& Tip); UFUNCTION(BlueprintImplementableEvent) void TestText(const FText& Text); UFUNCTION(BlueprintImplementableEvent) void TestName(const FName& Name); UFUNCTION(BlueprintImplementableEvent) void TestArray(const TArray<int32>& Array);

修改以后即可正常编译。


七、原因分析

从实验结果来看,可以得到下面几个结论。

1、UHT 会自动优化大型对象参数

对于:

  • FString

  • FText

  • FName

  • TArray

  • TMap

  • 大部分 UStruct

UHT 在生成代码时,会采用:

const Type&

而不是值传递。

例如:

自己写:

void Foo(FString Str);

UHT 实际生成:

void Foo(const FString& Str);

这样可以避免 Blueprint 调用时产生一次对象拷贝。


2、基础类型不会修改

例如:

int32 float bool

依旧保持值传递。

因此不会出现签名问题。


3、BlueprintCallable 与 BlueprintImplementableEvent 的区别

BlueprintCallable

属于普通 C++ 函数。

必须自己实现。

UFUNCTION(BlueprintCallable) void Foo(int32 Value);

必须有:

void UMyClass::Foo(int32 Value) { }

否则一定出现:

LNK2019

BlueprintImplementableEvent

实现由 Blueprint 完成。

不需要 cpp。

但是参数类型必须与 UHT 生成的一致。

否则会出现:

C2511

八、推荐写法

建议以后所有 UFUNCTION 都遵循 Epic 的代码风格。

基础类型

int32 float bool FVector FRotator

直接值传递即可。

例如:

void Foo(int32 Value);

大型对象

统一使用 const 引用:

const FString& const FText& const FName& const TArray<T>& const TMap<K,V>& const FMyStruct&

例如:

UFUNCTION(BlueprintCallable) void SetName(const FString& Name); UFUNCTION(BlueprintImplementableEvent) void OnDataLoaded(const TArray<int32>& Data); UFUNCTION(BlueprintNativeEvent) void OnTextChanged(const FText& Text);

这样既符合 Epic 官方源码风格,也能避免 UHT 自动生成参数时出现签名不一致的问题。


九、最终建议

对于UE5.7.1 源码版开发,建议统一遵循下面的规范:

参数类型推荐写法
int32int32
floatfloat
boolbool
FStringconst FString&
FTextconst FText&
FNameconst FName&
TArrayconst TArray<T>&
TMapconst TMap<K,V>&
UStructconst FMyStruct&

按照这种方式编写UFUNCTION,既符合 Unreal Engine 的代码规范,也可以避免参数签名带来的编译问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询