我们知道,在原生的C++编程中,函数在进行值传递的时候,其实是利用了外部的一个值变量,调用class的拷贝构造函数,创建了一个临时变量,如下一段代码
struct A
{
int i = 0;
A()
{
cout << "A constructor" << endl;
}
A(const A& Other)
{
cout << "A copy constructor" << endl;
}
A& operator=(const A& Other)
{
cout << "A operator" << endl;
return *this;
}
};
void PrintStruct(A& a)
{
a.i = 1;
}
void PrintStruct2(A a)
{
a.i = 1;
}
int main()
{
A a;
PrintStruct(a);
PrintStruct2(a);
return 0;
}
输出为
第一行的输出为a的构造输出,这正常,PrintStruct因为是引用传递,所以没有构造新的变量,没有输出,PrintStruct2因为是值传递,所以用a作为参数,调用了A的拷贝构造函数生成了一个临时变量给函数使用。
稍微熟悉UE4蓝图的同学都知道,在UE4的蓝图函数中,既可以使用值传递,也可以使用引用传递,那么它的运行方式是不是和原生C++一样呢?经过我的测试,并不一样。
为了测试这个问题,我们新建一个UE4工程后,添加如下测试结构体
USTRUCT(BlueprintType)
struct FTestStruct
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadWrite, EditAnyWhere)
int value1;
public:
FTestStruct();
FTestStruct(const FTestStruct& Other);
FTestStruct& operator = (const FTestStruct& Other);
};
FTestStruct::FTestStruct()
{
value1 = 3;
GameLog(Log, TEXT("FTestStruct constructor. value1 = %d"), value1);
}
FTestStruct::FTestStruct(const FTestStruct& Other)
{
value1 = Other.value1;
GameLog(Log, TEXT("FTestStruct copy constructor. value1 = %d"), value1);
}
FTestStruct& FTestStruct::operator = (const FTestStruct& Other)
{
value1 = Other.value1;
GameLog(Log, TEXT("FTestStruct operator. value1 = %d"), value1);
return *this;
}
可以看出,我们为FTestStruct添加了构造函数、拷贝构造函数、赋值操作符,并且在对应的函数中打印了输出。
打开编辑器后,我们在一个Actor蓝图中添加结构体变量MemTestStruct,设置value=10,并添加函数PrintStruct(FTestStruct),即函数的参数为值传递。GamePlay中调用PrintStruct,如下图
?在这个断点之后,看到输出为
FTestStruct constructor. value1 = 3 FTestStruct operator. value1 = 10
这说明了什么呢?从这个现象看,蓝图函数的值传递做法和C++原生的函数值传递做法不一样,蓝图函数是先构造了一个临时变量,所以先有构造函数的输出,然后用外部的值向临时变量赋值,于是便调用了class的赋值函数,就有了operator的输出,并不是原生C++一次性的拷贝构造。
当我把值传递改成引用传递后,便没有了输出,说明引用传递确实如其名,没有临时的对象生成。
在测试这个问题的过程中,我还发现了一个额外的有趣的现象,那就是我给Actor加了一个MemTestStruct变量并设置value=10之后,什么也不做(不添加PrintStruct这种测试函数),运行游戏,Actor生成的时候会有如下输出
FTestStruct constructor. value1 = 3 FTestStruct operator. value1 = 10
这个现象这么解释?现在我还没深入研究底层代码,但从这个现象来看,第一行的构造输出是Actor本身生成时候的Struct构造输出。关于第二行输出的解释,我们知道,编辑Actor之后,其实Actor本身是作为一种资产保存在磁盘上的,那么value=10其实也是被保存在了磁盘中,之后加载的时候,用这块数据向向MemTestStruct赋值,便有了operator的输出。由此可知,Actor里面的结构体,其实在运行游戏的时候有两个过程,一个是本身构造,另一个是赋值操作。
|