At first, when I used TMap<FString, std::function <void(void)>>, I found that when inserting the fifth value (TMap is based on TArray, and the default initialization is 4), the previously inserted value will not valid.
After debugging, I was found that TArray did not call the Copy Constructor or Assignment Operator when ResizeAllocation (expanding memory).
To verify the problem, I build a structure to reproduce the problem.
代码如下:
The code is as follows:
struct MyStruct
{
int Name;
int* NameP;
MyStruct()
: Name(0), NameP(&Name)
{
}
MyStruct(const int& InName)
: Name(InName)
, NameP(&Name)
{
}
MyStruct(const MyStruct& Oth)
: Name(Oth.Name)
, NameP(&Name)
{
}
MyStruct& operator = (const MyStruct& Oth)
{
Name = Oth.Name;
}
};
TArray<MyStruct> Structs;
Structs.Emplace(111);
Structs.Emplace(222);
Structs.Emplace(333);
Structs.Emplace(444);
/// after this line, memory reallocate,
/// previously items's NameP pointer to a bad memory address
Structs.Emplace(555);
The MyStruct has a int* member NameP, pointer to the member Name
When TArray need ‘ResizeAllocation’ (add, insert, resize or other operators),
TArray ResizeAllocation memory and not call OLD items’s Copy Constructor or Assignment Operators, the OLD items’s NameP is old and not valid.
TSet and TMap based TArray, the same problem exists!
This is not a bug. This is the premise of TArray. TArray docs A dynamically sized array of typed elements. Makes the assumption that your elements are relocate-able; i.e. that they can be transparently moved to new memory without a copy constructor.
(yes, some people still think switching to STL makes no sense)
TArray<MyStruct> Structs;
int32 index;
index = Structs.Emplace(111);
index = Structs.Emplace(222);
index = Structs.Emplace(333);
index = Structs.Emplace(444);
// There is no problem with the above
index = Structs.Emplace(555);
if(Structs[0].NameP != &Structs[0].Name){ UE_LOG(LogTemp, Warning, TEXT("Not the same!")); }
if (Structs[1].NameP != &Structs[1].Name) { UE_LOG(LogTemp, Warning, TEXT("Not the same!")); }
if (Structs[2].NameP != &Structs[2].Name) { UE_LOG(LogTemp, Warning, TEXT("Not the same!")); }
if (Structs[3].NameP != &Structs[3].Name) { UE_LOG(LogTemp, Warning, TEXT("Not the same!")); }
// There is no problem with the 5th one
if (Structs[4].NameP != &Structs[4].Name) { UE_LOG(LogTemp, Warning, TEXT("Not the same!")); }
I know how to solve the problem.
I checked the source code of the UE and did not call the constructor.
I just want to confirm whether TArray is designed in this way.