(super:) —— Verse 里最容易写错的调用
重写了父类方法,又想把父类的原实现叫回来「加戏」——这是从 C++ / Blueprint 转来的开发者必问的问题。答案只有一行,但形状怪到几乎没人第一次能写对:(super:)。
一、场景:重写之后,父类的台词去哪了?
第 16 课学过:子类用 <override> 重写父类方法后,调用时执行的就是子类的新版本,父类的实现被完全「顶掉」了。但实战里更常见的需求是「加戏」而不是「换戏」:父类的欢迎词照常播,子类再追加一句 VIP 专属台词。Epic 开发者论坛上的经典提问就是这个场景——父类 Test() 打印 Parent,子类重写后打印 Child,提问者想要两句都打印。
在 C++ 里你会写 Super::Test(),在很多语言里是 super.Test()——把这两种肌肉记忆先放下,Verse 的写法是圆括号加冒号:
如果你平时只在蓝图里连节点:你想在 Override 过的函数里保留父类原来的那套行为,做法是右键选「Add Call to Parent Function」。Verse 没有这个右键菜单,改用一小段写法——圆括号加冒号:
using { /UnrealEngine.com/Temporary/Diagnostics }
greeter := class:
Greet():void =
Print("父类:欢迎来到竞技场!")
vip_greeter := class(greeter):
Greet<override>():void =
# 先执行父类的原实现,再追加子类的新逻辑
(super:)Greet()
Print("子类:VIP 通道这边请~")
读图翻译:vip_greeter 是 greeter 的子蓝图,它 Override 了 Greet 函数图。函数图开头那句 (super:)Greet() 就相当于蓝图里的「Add Call to Parent Function」——先把父类那句欢迎词跑一遍,接着再接自己的 Print String 打出 VIP 台词。所以运行时你会看到两行:先父类的、后子类的。
调用 vip_greeter{}.Greet(),日志会依次打出两行:先是父类的欢迎词,再是 VIP 台词。(super:)Greet() 读作「到父类的作用域里,找 Greet 来执行」读作「到父类那一层去,把父类的 Greet 找出来跑」——这正是论坛帖中被采纳的答案,答主同时给出了官方 Subclass in Verse 文档佐证。
二、为什么是括号加冒号?
(super:) 的形状在主流语言里确实罕见,但放进 Verse 的语法体系里就顺眼了:它不是一个「特殊关键字调用」,而是一个作用域限定符(scope qualifier)——「括号 + 名字 + 冒号」这一家族在 Verse 里专门负责回答「去哪个作用域找这个标识符」,比如 (local:) 用来限定局部作用域。(super:)Greet() 的语义因此非常直白:不要在当前类里找 Greet(那会找到自己,变成无限递归),而是到直接父类的作用域里找。
别被 (super:) 这个怪模样吓到,它的意思其实很土:就是「去父类那一层找」。Verse 里「圆括号 + 名字 + 冒号」是一整套「到哪一层去找」的写法(还有个 (local:),意思是「就在本地这一层找」)。(super:)Greet() 说白了:别在自己这张蓝图里找 Greet(找到的还是自己,等于函数图自己无限调自己、直接卡死),而是往上到那一层父蓝图里,把父类的 Greet 找出来跑。
两条使用限制要记牢:第一,(super:) 只能出现在重写方法的内部——在类外部或没有重写关系的方法里,它没有意义;第二,它调用的是直接父类的版本,你无法用它「跳级」去调用祖父类的实现。日常最典型的用法,就是在重写引擎回调时保留父类行为在 Override 引擎事件(比如 BeginPlay)时保留父类那套行为、再叠加自己的逻辑,思路与本例的 Greet 完全相同。
顺手排雷,以下写法全部编译不过:super.Greet()(Verse 没有 super 这个独立标识符)、Super::Greet()(没有 :: 运算符)、super:Greet()(少了括号)。只有 (super:)Greet()——括号、super、冒号、紧跟方法调用,一个零件都不能少。
顺手排雷:下面这些写法在 Verse 里全都 Compile 报红——super.Greet()、Super::Greet()、super:Greet()(它们是别的语言/引擎的写法)。只有 (super:)Greet() 是对的:圆括号、super、冒号、紧跟着要调的那个函数,少一个零件都不行。
三、常见坑:(super:) 报错,病根常在重写上
先把各家语言的「肌肉记忆」摆在一起对照,换脑子会快很多:
如果你之前只在蓝图里连节点,下面这张「别的语言怎么写」的对照表扫一眼就够了——真正要记的只有最后一行 Verse 的写法;上面几行是给从代码转来的人换脑子用的,你用不上。
| 语言 | 调用父类实现 | 备注 |
|---|---|---|
| C++(UE 惯例) | Super::Test() |
双冒号;Super 是父类的别名 |
| Java / JavaScript | super.test() |
点号成员访问 |
| C# | base.Test() |
关键字换成了 base |
| Verse | (super:)Test() |
作用域限定符,括号与冒号缺一不可「去父类那一层找」的写法,括号和冒号都不能省 |
另一类高频报错其实不在 (super:) 本身,而在它的前提——重写。Verse 要求重写必须显式声明:忘了给子类方法加 <override>,编译器会报「成员已在父类中定义」一类的错误Compile 会报红,说这个成员父类已经有了,因为它把你的新方法当成了非法重名;反过来,给父类里根本不存在的方法加 <override> 同样报错——没有可以重写的对象。签名也必须与父类完全一致,参数或返回类型对不上都过不了编译函数签名也得和父类一模一样——输入、输出引脚对不上就 Compile 报红。这三关全过了,(super:) 才有落脚点:它只能站在一个合法的重写方法里,指向被你顶掉的那份原实现。
四、随堂小测 ◇
子类重写了 Describe(),想在新实现里先执行父类的原版本,正确写法是?
关于 (super:) 的使用范围,下面哪句是对的?
来源与延伸
本文整理自 Epic 开发者论坛与官方文档:
▸ 论坛问答:How to call parent function from an overridden function ↗