UE_Angelscript 认知篇——理解:复制、引用#

前言#

本文解释关于 UE-Angelscript 脚本语言使用中,最好具备的认知基础——复制、引用。

C ++ 基础概念#

学过一点 C++ 的,多多少少都知道 C++ 在函数参数传递中,默认是按值传递(复制)的,想要操作原来的对象,要么用原对象,要么用指针,要么用引用。

什么是原对象? 就是值本身。对象一般可以理解为,连续的一块内存,刻画了对象的具体细节。 (一块代表某种事物的内存,可以有初始的名字,比如张三。)

什么是指针? 基础概念,是一个保存了一个内存地址的对象,不多解释了。 指针的概念,带来一个好处——操作数据方便,只要传对象的地址定位,就可以操作对象了。 (其本身也是一个对象,但是这个对象很特殊,它保存着其他对象的地址,比如,对张三的 GPS 定位,通过定位,我们可以精准地找到张三。)

什么是引用? 是个对象的别名,操作引用,就和操作原对象一样。 引用的底层实现本质上也是指针,只是语法上引用不能重新绑定,且解引用是隐式的;在 C++ 里,一般使用者可以这么认为,那是编译器的事情,只需要理解为别名就好了。但是涉及到 Angelscript 与 C++ 的交互时,引用本质是指针的情况,是不能忽略的,否则,会产生难以理解的一些问题。 (在使用者的概念上,就是对象的别名,别名是什么意思?比如,张三是对象原名,那么我们可以其一个张三的别名——张铁柱,别名可以很多的,都代表了张三这个对象。)

何为复制? 将一个对象完整地复制一份出来,就是复制。 (我们再说说张三这个对象,如果复制,那么就会成为李四,这时候,就同时存在张三和李四,虽然刚复制完成时,他们两个是一模一样的,但是后续的人生就各走各的了。)

为何要区分复制和引用。 复制需要性能和存储开销的,一般情况下,当然是能不复制最好了。引用就是在这个时候出现的,一个对象到了新环境(比如某个函数),这个环境并不在意原来对象叫什么,进来的对象,只要坑位对了,那么统一为叫做某个名字或者代码,比如 9527。 (张三如果进入新环境,被叫做狗蛋或者 9527,都没办法,张三身不由己,只能入乡随俗,虚拟的张三,只是提线木偶,莫得感情,更别说自由,只是希望人类在有好处的时候,别给错了复制出来的张三分身。)

UE 与 Angelscript 的数据对应关系#

值类型#

在 UE 与 Angelscript 的数据对应中,所有的基础类型,都是值类型对应,比如 int 和 float 等, 你在 AS 里操作的这些类型,都是操作值本身。

还有很多绑定的类型,比如 FString FName FColor FVector 等。 一句话总结,除了 UObject 及其子类外,UE-Angelscript 的类型都是值类型。

Angelscript 与 C++ 写法一致 比如 FTestStruct

代理类型#

(或者叫做指针类型、引用类型,反正不是对象本身,而是对象的地址。)

目前已知只有 UObject 及其子类绑定为代理类型,在 Angelscript 里,这种代理类型对应为 Handle (句柄),实际上,Angelscript 对这种类型的完整写法是 UObject@,不过,UE-Angelscript 脚本绑定的时候,声明它可以省略 @, 于是大家都忽略掉@,这造成一个视觉问题——所有的 UObject 就看着和值类型一样,并无特殊标记,然而它们确实不是同一类。

对于 Angelscript 与 C++ 写法不一致 在 Angelscript 里写 UObject, 对应为 C++ 里的UObject*。

模板类型——三大容器 TArray TMap TSet#

可以理解为复合类型,比如 TArray<UObject>, 主类型是 TArray,子类型是 UObject 。

对于值类型,Angelscript 与 C++ 写法一致 比如 TArray<FTestStruct>

对于 UObject, Angelscript 与 C++ 写法不一致 在 Angelscript 里写 TArray<UObject>, 对应为 C++ 里的TArray<UObject*>。

TMap 和 TSet 同理。

另外还有迭代器 TArrayConstIterator、TArrayIterator 等这种容器的迭代器,也是差不多的。

模板类型—— UObject 指针#

TObjectPtr 、TWeakObjectPtr、TSoftObjectPtr 等

可以理解为复合类型,比如 TObjectPtr<UObject>, 主类型是 TObjectPtr,子类型是 UObject 。

Angelscript 与 C++ 写法一致 比如TObjectPtr<UObject>

模板类型——其他#

一般不用于 UObject。

可以理解为复合类型,比如 TOptional<FTestStruct>, 主类型是 TOptional,子类型是 FTestStruct 。 TOptional 不用于持有 UObject,只用于值类型。(原因在于 UObject 可以用 nullptr 表达空状态,无需多此一举。)

Angelscript 与 C++ 写法一致 比如TOptional<FTestStruct>

Angelscript 基本和 C++的复制与引用规则基本一致#

特例除外#

例外,对于 UStruct 以及 FString\FName\FText,在函数编写的时候,只要参数类型不是单独的可变引用&,那么传参的时候,默认就会成为 const &。

一下四个函数中,有三个是等价的! 函数 GetDataPtr,等价于 GetDataPtr_InputConst ,等价于 GetDataPtr_InputConstRef

	FString GetDataPtr(FTestStructInAS A)
	{
		return FDssDataPtr::Create(A).ToString();
	}

	FString GetDataPtr_InputConst(const FTestStructInAS A)
	{
		return FDssDataPtr::Create(A).ToString();
	}

	FString GetDataPtr_InputRef(FTestStructInAS& A)
	{
		return FDssDataPtr::Create(A).ToString();
	}

	FString GetDataPtr_InputConstRef(const FTestStructInAS& A)
	{
		return FDssDataPtr::Create(A).ToString();
	}

其余情况,和 C++的规则是一样的。

虽说如此,在 UE-Angelscript 的官方文档也这么写,但是,那该如何证明呢? 证明的逻辑,是这样的:一个值如果被传入函数中,如果是被复制,其地址会更改,我们只需要比较地址,就知道是不是一样了。

代码如下。 注:其中用到了 FDataPtr 是一个存指针的结构体,其 ToString() 函数会将指针转为 FString,而FDataPtr::Create 这种需要自定义绑定的特殊函数,需要会绑定泛型函数才可以操作,涉及其他比较深一点的绑定技巧,这里不详解释,所以,以下就当作伪代码吧。

namespace TestGetDataPtr
{
	/// #tag UStruct
	// UStruct in AS is a value type in AS, it needs to be passed by reference to improve performance. We don't want to add const & every time, so we make it as const & in function by default.
	FString GetDataPtr(FTestStructInAS A)
	{
		return FDataPtr::Create(A).ToString();
	}
	// const UStruct params will be added  & too
	FString GetDataPtr_InputConst(const FTestStructInAS A)
	{
		return FDataPtr::Create(A).ToString();
	}
	FString GetDataPtr_InputRef(FTestStructInAS& A)
	{
		return FDataPtr::Create(A).ToString();
	}

	FString GetDataPtr_InputConstRef(const FTestStructInAS& A)
	{
		return FDataPtr::Create(A).ToString();
	}
}

// 测试代码

// #tag  UStruct function
{
	auto Obj = FTestStructInAS();
	auto DataPtr = FDataPtr::Create(Obj).ToString();

	// by default,the function Struct params will be added const &
	auto GetDataPtr = TestGetDataPtr::GetDataPtr(Obj);
	T.AssertTrue(GetDataPtr == DataPtr);

	auto GetDataPtr_InputRef = TestGetDataPtr::GetDataPtr_InputRef(Obj);
	T.AssertTrue(GetDataPtr_InputRef == DataPtr);

	auto GetDataPtr_InputConstRef = TestGetDataPtr::GetDataPtr_InputConstRef(Obj);
	T.AssertTrue(GetDataPtr_InputConstRef == DataPtr);

	// by default,the function Struct params with const will be added  &
	auto GetDataPtr_InputConst = TestGetDataPtr::GetDataPtr_InputConst(Obj);
	T.AssertTrue(GetDataPtr_InputConst == DataPtr);
}

同理,可以检验 FString\FName\FText 也是如此。

而其余类型,就是默认的 Cpp 规则了。

为什么有这样的特例呢?优化性能、简化书写吧。因为如果一个 UStruct 如果占用内存很大,那么复制的开销是很大的。 UObject 占用的内存一般更大,怎么不优化它?Angelscript 的 UObject 本质上是一个指针而已,传的都是指针,复制就复制吧,不会有什么性能压力。 还有,基础类型比如 int 等呢?这些类型占据的内存也很小,复制也无所谓了。

还是有较大复制开销的情况需要注意#

TArray<FTestStruct> 这种值类型,每当调用 Add 函数,都会复制一次 FTestStruct。

把它取出来的时候,新手可能会忘记加上 &,那么,也会复制一次。

复制本身可能只是性能开销,但如果你的目的是修改容器内的元素,复制出来的只是副本,那么修改不会影响原容器中的值。

所以,承接容器里的值,不想产生不必要的复制,建议还是这样吧

TArray<FTestStruct> TempArray;
TempArray.Add(FTestStruct()); // 确保非空
const auto& TempConst = TempArray[0];
auto& TempMutable = TempArray[0];

值得注意的是,UObject 始终是特例,如果是 TArray<UObject>这种,因为 UObject 本质上是指针,就算复制了,也只是复制了指针而已,还是可以操作到实际的 UObject 对象的。


总结#

即使是写脚本,即使是 UE-Angelscript 特意优化了函数传参,如果引用和复制的规则不清晰,还是可能会导致大量复制的出现,特别是在使用容器的时候。

当然,复制的出现只是耗费点性能,然而如果将复制当作引用来使用,那么,业务逻辑就问题大了。

所以啊,既然表面上没有了指针的存在,那么——复制的归复制,引用的归引用,就像凯撒名言——“凡属于凯撒的,应归凯撒;凡属于上帝的,应归上帝。”

别混淆了两者的边界,不然就陷入 指鹿为马 的泥潭。

当然,读者聪慧如斯,自然可以理解二者差异了。


×

联系

问题反馈

问题与反馈请提交至 GitHub Issues。

合作

合作事宜请发送邮件至 contact@cuviss.com。

×

关于

服务条款

各插件的许可证见社区贡献页。

网站内容可在注明来源后引用。

软件和文档按“现状”提供,不作任何保证。

Unreal Engine 等第三方项目及名称相关商标归各自所有者所有。soloAS 与 Epic Games、Hazelight 均无关联。

致谢和案例展示请求均不具约束力,详见首页说明。

更新于 2026-10-24

隐私政策

本网站不使用分析工具、Cookie 或跟踪,也没有数据表单或第三方脚本。

网站托管于 GitHub Pages。GitHub 可能按其隐私声明处理标准访问日志。

通过邮件发送的内容会提供给我们;通过 GitHub Issues 发送的内容会提供给 GitHub。

更新于 2026-10-24

目录

页面列表

本页目录