Depois de TArray, o contêiner mais usado na Unreal Engine é TMap. TMap é semelhante a TSet, pois sua estrutura é baseada em chaves de hash. No entanto, diferente de TSet, TMap armazena dados como par chave-valor (TPair<KeyType, ValueType>), usando chaves apenas para armazenamento e recuperação.
Tipos de mapas na Unreal Engine
Há dois tipos de mapa na Unreal Engine:
Visão geral do TMap
Em um TMap, os pares chave-valor são tratados como o tipo de elemento do mapa, como se cada par fosse um objeto individual. Neste documento, elemento significa um par chave-valor, enquanto componentes individuais são referidos como a chave do elemento ou o valor do elemento.
O tipo de elemento é um
TPair<KeyType, ElementType>, embora seja raro precisar fazer referência direta ao tipo TPair.As chaves TMap são exclusivas.
Semelhante a TArray, TMap é um contêiner homogêneo, o que significa que todos os elementos são estritamente do mesmo tipo.
TMap é um tipo de valor e suporta as operações usuais de cópia, atribuição e destruição, bem como a propriedade forte de seus elementos, que são destruídos quando o mapa é destruído. A chave e o valor também devem ser tipos de valores.
TMap é um contêiner de hash, o que significa que o tipo de chave deve ser compatível com a função GetTypeHash e fornecer um
operator==para comparar a igualdade de chaves
TMap e TMultimap (como muitos contêineres da Unreal Engine) assumem que o tipo de elemento é deslocável de forma trivial, significando que os elementos podem ser movidos com segurança de uma posição na memória para outra copiando bytes brutos.
Visão geral de TMultiMap
Aceita o armazenamento de várias chaves idênticas.
Ao adicionar um par chave-valor a um TMap com uma chave que corresponde a um par existente, o novo par substituirá o antigo.
Em um TMultiMap, o contêiner armazena o novo par e o antigo.
O TMap pode usar um alocador opcional para controlar o comportamento de alocação de memória. No entanto, diferente de TArray, estes são alocadores set em vez dos alocadores padrão da Unreal, como FHeapAllocator e TInlineAllocator. Alocadores Set, (TSetAllocator), definem quantos buckets de hash o mapa deve usar e quais alocadores padrão da UE devem ser usados para armazenamento de hash e elementos.
O último parâmetro de modelo de TMap é KeyFuncs, que informa ao mapa como recuperar a chave do tipo de elemento, como comparar a igualdade entre duas chaves e como gerar o hash da chave. Elas têm padrões que retornam uma referência à chave e usam operator== para igualdade e chamam a função GetTypeHash não membro para hash. Se o tipo de chave for compatível com essas funções, você poderá usá-la como uma chave de mapa sem fornecer uma KeyFuncs personalizada.
Ao contrário de TArray, a ordem relativa dos elementos de TMap na memória não é confiável nem estável, e iterar sobre os elementos provavelmente os retornará em uma ordem diferente da ordem em que foram adicionados. É improvável que os elementos sejam organizados de forma contígua na memória.
A estrutura de dados base de um mapa é uma matriz esparsa, que é uma matriz que suporta espaços entre seus elementos de forma eficiente. À medida que os elementos são removidos do mapa, aparecerão lacunas na matriz esparsa. Adicionar novos elementos à matriz pode preencher essas lacunas. No entanto, embora TMap não embaralhe os elementos para preencher as lacunas, os ponteiros para os elementos ainda podem ser invalidados, pois todo o armazenamento pode ser realocado quando estiver cheio e novos elementos forem adicionados.
Criar e preencher um mapa
O código a seguir cria uma TMap:
TMap<int32, FString> FruitMap;FrutMap agora é uma TMap vazio com strings identificadas por chaves de inteiro. Como não especificamos um alocador nem um KeyFuncs, o mapa executa a alocação de heap padrão e compara a chave do tipo int32 usando operator== e transforma a chave em hash usando GetTypeHash. Nenhuma memória foi alocada até agora.
Adicionar
A maneira padrão de preencher um mapa é chamar Adicionar com uma chave e um valor:
FruitMap.Add(5, TEXT("Banana"));
FruitMap.Add(2, TEXT("Grapefruit"));
FruitMap.Add(7, TEXT("Pineapple"));
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Grapefruit" },
// { Key: 7, Value: "Pineapple" }
// ]Embora os elementos estejam listados aqui na ordem de inserção, não há garantia da ordem real deles na memória. Para um novo mapa, é provável que estejam na ordem de inserção, mas à medida que mais inserções e remoções acontecem, torna-se cada vez mais improvável que novos elementos apareçam no final.
Isso não é TMultiMap e, portanto, as chaves são exclusivas. Este é o resultado da tentativa de adicionar uma chave duplicada:
FruitMap.Add(2, TEXT("Pear"));
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" }
// ]O mapa ainda contém três elementos, mas o valor anterior de Grapefruit com chave 2 foi substituído por Pear.
A função Add pode aceitar uma chave sem um valor. Quando a função Add sobrecarregada for chamada, o valor será construído como padrão:
FruitMap.Add(4);
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "" }
// ]Emplace
Como em TArray, podemos usar Emplace em vez de Add para evitar a criação de temporários ao inserir no mapa:
FruitMap.Emplace(3, TEXT("Orange"));
// FruitMap == [
// { Key: 5, Value: "Banana" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "" },
// { Key: 3, Value: "Orange" }
// ]Aqui, a chave e o valor são passados diretamente aos seus respectivos construtores de tipo. Embora isso não seja significativo para a chave int32, ele evita a criação de uma FString temporária para o valor. Ao contrário de TArray, só é possível inserir elementos em um mapa com construtores de argumento único.
Anexar
Você pode combinar dois mapas com a função Append, que move todos os elementos do mapa de argumento para o mapa de objeto de chamada:
TMap<int32, FString> FruitMap2;
FruitMap2.Emplace(4, TEXT("Kiwi"));
FruitMap2.Emplace(9, TEXT("Melon"));
FruitMap2.Emplace(5, TEXT("Mango"));
FruitMap.Append(FruitMap2);
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "Kiwi" },
No exemplo acima, o mapa resultante equivale a usar Add ou Emplace para adicionar cada elemento de FruitMap2 individualmente, esvaziando FruitMap2 quando o processo for concluído. Isso significa que qualquer elemento de FruitMap2 que compartilhar a chave com um elemento que já esteja em FrutMap substituirá esse elemento.
Se você marcar TMap com o macro UPROPERTY e uma das palavras-chave editáveis (EditAnywhere, EditDefaultsOnly ou EditInstanceOnly), poderá adicionar e editar elementos no editor.
UPROPERTY(EditAnywhere, Category = MapsAndSets)
TMap<int32, FString> FruitMap;Iterar
A iteração em TMaps é semelhante a TArrays. Você pode usar a funcionalidade ranged-for do C++, lembrando que o tipo de elemento é um TPair:
for (auto& Elem : FruitMap)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
Elem.Key,
*Elem.Value
)
);
}
Você pode criar iteradores com as funções CreateIterator e CreateConstIterator.
| Função | Descrição |
|---|---|
| Retorna um iterador com acesso de leitura e gravação. |
| Retorna um iterador somente leitura. |
Em ambos os casos, você pode usar as funções Key e Value desses iteradores para examinar os elementos. A impressão do conteúdo do nosso exemplo FruitMap usando iteradores ficaria assim:
for (auto It = FruitMap.CreateConstIterator(); It; ++It)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
It.Key(), // same as It->Key
*It.Value() // same as *It->Value
)
);
}Obter valor
Se você souber que o mapa contém uma determinada chave, pode procurar o valor correspondente com operator[], usando a chave como índice. Fazer isso com um mapa não constante retorna uma referência não constante, enquanto um mapa constante retorna uma referência constante.
Sempre verifique se o mapa contém a chave antes de usar operator[]. Se o mapa não tiver a chave, ele será ativado.
FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
FString Val8 = FruitMap[8];
// Assert!Consulta
Para determinar quantos elementos há em um TMap, chame a função Num:
int32 Count = FruitMap.Num();
// Count == 6Para determinar se um mapa contém uma chave específica, chame a função Contains:
bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == falseSe não tiver certeza se o mapa contém uma chave, verifique usando a função Contains e, em seguida, use operator[]. No entanto, isso não é o ideal, pois uma recuperação bem-sucedida envolve duas pesquisas na mesma chave.
A função Find combina esses comportamentos com uma única pesquisa. Find retorna um ponteiro para o valor do elemento quando o mapa contém a chave, ou um ponteiro Nulls quando não a contém. Chamar Find em um mapa constante retorna um ponteiro constante.
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptrOutra opção, para garantir um resultado válido da consulta, é usar FindOrAdd ou FindRef:
| Função | Descrição |
|---|---|
| Retorne uma referência ao valor associado com a chave fornecida. Se a chave não estiver no mapa,
|
| Apesar do nome, retorna uma cópia do valor associado à chave, ou um valor padrão se a chave não estiver no mapa. |
Como FindOrAdd e FindRef têm sucesso mesmo quando a chave não é encontrada no mapa, você pode chamá-los com segurança sem os procedimentos de segurança usuais, como verificar Contains antecipadamente ou verificar se o valor de retorno é nulo.
FString& Ref7 = FruitMap.FindOrAdd(7);
// Ref7 == "Pineapple"
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
Como FindOrAdd pode adicionar novas entradas ao mapa, como ocorre na inicialização de Ref8 neste exemplo, ponteiros ou referências obtidas anteriormente podem se tornar inválidas. Isso é o resultado da operação de adição que aloca memória e move dados existentes se o armazenamento de back-end do mapa precisar expandir para conter o novo elemento. No exemplo acima, Ref7 pode ser invalidada após Ref8 após a chamada para FindOrAdd(8).
A função FindKey realiza uma pesquisa inversa, o que significa que um valor fornecido é correspondido a uma chave e retorna um ponteiro para a primeira chave emparelhada com o valor fornecido. A pesquisa de um valor que não está presente no mapa retorna um ponteiro nulo.
const int32* KeyMangoPtr = FruitMap.FindKey(TEXT("Mango"));
const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat"));
// *KeyMangoPtr == 5
// KeyKumquatPtr == nullptrPesquisas por valor são mais lentas (tempo linear) do que pesquisas por chave. Isso ocorre porque o mapa é transformado em hash por chave, não por valor. Além disso, se um mapa tiver várias chaves com o mesmo valor, FindKey poderá retornar qualquer uma delas.
As funções GenerateKeyArray e GenerateValueArray preenchem uma TArray com uma cópia de todas as chaves e valores, respectivamente. Em ambos os casos, a matriz transmitida é esvaziada antes do preenchimento e, portanto, o número de elementos resultantes sempre será igual ao número de elementos no mapa.
TArray<int32> FruitKeys;
TArray<FString> FruitValues;
FruitKeys.Add(999);
FruitKeys.Add(123);
FruitMap.GenerateKeyArray (FruitKeys);
FruitMap.GenerateValueArray(FruitValues);
// FruitKeys == [ 5,2,7,4,3,9,8 ]
// FruitValues == [ "Mango","Pear","Pineapple","Kiwi","Orange",
// "Melon","" ]Remover
Você pode remover elementos de um mapa usando a função Remove e fornecendo a chave do elemento a ser removido. O valor de retorno é o número de elementos que foram removidos e pode ser zero se o mapa não contiver nenhum elemento correspondente à chave.
FruitMap.Remove(8);
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 7, Value: "Pineapple" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]Remover elementos pode deixar lacunas na estrutura de dados, que você pode ver ao visualizar o mapa na janela de observação do Visual Studio, mas foram omitidas aqui para maior clareza.
A função FindAndRemoveChecked pode ser usada para remover um elemento do mapa e retornar seu valor. A parte "checked" do nome indica que o mapa chama a verificação se a chave não existir.
FString Removed7 = FruitMap.FindAndRemoveChecked(7);
// Removed7 == "Pineapple"
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 2, Value: "Pear" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
A função RemoveAndCopyValue é semelhante a Remove, mas copia o valor do elemento removido para um parâmetro de referência. Se a chave especificada não estiver presente no mapa, o parâmetro de saída permanecerá inalterado, e a função retornará false.
FString Removed;
bool bFound2 = FruitMap.RemoveAndCopyValue(2, Removed);
// bFound2 == true
// Removed == "Pear"
// FruitMap == [
// { Key: 5, Value: "Mango" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
Por fim, você pode remover todos os elementos do mapa com as funções Empty ou Reset.
TMap<int32, FString> FruitMapCopy = FruitMap;
// FruitMapCopy == [
// { Key: 5, Value: "Mango" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" },
// { Key: 9, Value: "Melon" }
// ]
FruitMapCopy.Empty(); // You can also use Reset() here.
// FruitMapCopy == []Empty pode usar um parâmetro para indicar a quantidade de margem deixada no mapa, e Reset sempre deixa a maior margem possível.
Classificar
Você pode classificar um TMap por chave ou por valor. Após a classificação, a iteração no mapa apresenta os elementos na ordem classificada, mas esse comportamento só é garantido até a próxima vez que você modificar o mapa. A classificação é instável, portanto, os elementos equivalentes em um TMultiMap podem aparecer em qualquer ordem.
Você pode classificar por chave ou por valor usando as funções KeySort ou ValueSort, respectivamente. Ambas as funções usam um predicado binário que especifica a ordem de classificação.
FruitMap.KeySort([](int32 A, int32 B) {
return A > B; // sort keys in reverse
});
// FruitMap == [
// { Key: 9, Value: "Melon" },
// { Key: 5, Value: "Mango" },
// { Key: 4, Value: "Kiwi" },
// { Key: 3, Value: "Orange" }
// ]
Operadores
Como TArray, TMap é um tipo de valor regular e pode ser copiado com o constructor padrão ou o operador de atribuição. Como os mapas são proprietários dos seus elementos, copiar um mapa é profundo; o novo mapa terá sua própria cópia dos elementos.
TMap<int32, FString> NewMap = FruitMap;
NewMap[5] = "Apple";
NewMap.Remove(3);
// FruitMap == [
// { Key: 4, Value: "Kiwi" },
// { Key: 5, Value: "Mango" },
// { Key: 9, Value: "Melon" },
// { Key: 3, Value: "Orange" }
// ]
// NewMap == [
TMap permite semântica de movimento, que pode ser invocada usando a função MoveTemp. Após uma movimentação, o mapa de origem estará vazio:
FruitMap = MoveTemp(NewMap);
// FruitMap == [
// { Key: 4, Value: "Kiwi" },
// { Key: 5, Value: "Apple" },
// { Key: 9, Value: "Melon" }
// ]
// NewMap == []Slack
Slack é a memória alocada que não contém um elemento. Você pode alocar memória sem adicionar elementos chamando Reserve e pode remover elementos sem desalocar a memória que estavam usando chamando Reset ou chamando Empty com um parâmetro slack diferente de zero. Slack otimiza o processo de adicionar novos elementos ao mapa usando memória pré-alocada em vez de precisar alocar nova memória. Isso também pode ajudar na remoção de elementos, já que o sistema não precisa desalocar memória. Isso é especialmente eficaz ao esvaziar um mapa que espera ser preenchido imediatamente com o mesmo número de elementos ou menos.
TMap não fornece uma maneira de verificar quantos elementos estão pré-alocados como a função Max em TArray.
No código a seguir, a função Reserve aloca espaço para o mapa conter até dez elementos:
FruitMap.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{
FruitMap.Add(i, FString::Printf(TEXT("Fruit%d"), i));
}
// FruitMap == [
// { Key: 9, Value: "Fruit9" },
// { Key: 8, Value: "Fruit8" },
// ...
// { Key: 1, Value: "Fruit1" },
Para remover toda a folga de um TMap, use as funções Collapse e Shrink. Shrink remove todo o slack da extremidade do contêiner, mas deixa elementos vazios no meio ou no início.
for (int32 i = 0; i < 10; i += 2)
{
FruitMap.Remove(i);
}
// FruitMap == [
// { Key: 9, Value: "Fruit9" },
// <invalid>,
// { Key: 7, Value: "Fruit7" },
// <invalid>,
// { Key: 5, Value: "Fruit5" },
Shrink removeu apenas um elemento inválido do código acima, pois havia apenas um elemento vazio no final. Para remover todo o slack, use primeiro a função Compact para que os espaços vazios sejam agrupados na preparação para Shrink.
FruitMap.Compact();
// FruitMap == [
// { Key: 9, Value: "Fruit9" },
// { Key: 7, Value: "Fruit7" },
// { Key: 5, Value: "Fruit5" },
// { Key: 3, Value: "Fruit3" },
// { Key: 1, Value: "Fruit1" },
// <invalid>,
// <invalid>,
// <invalid>,
KeyFuncs
Se um tipo tiver um operator== e uma sobrecarga de GetTypeHash não membro, você poderá usá-lo como um tipo de chave para um TMap sem nenhuma alteração. No entanto, você pode querer usar tipos como chaves sem sobrecarregar essas funções. Nesses casos, você pode fornecer suas próprias KeyFuncs personalizadas. Para criar KeyFuncs para o seu tipo de chave, você deve definir duas typedefs e três funções estáticas, como segue:
| Definição de tipo | Descrição |
|---|---|
| Tipo usado para passar chaves. |
| Tipo usado para passar elementos. |
| Função | Descrição |
|---|---|
| Retorna a chave de um elemento. |
| Retorna |
| Retorna o valor hash de |
KeyInitType e ElementInitType são typedefs para a convenção normal de passagem do tipo de chave e do tipo de elemento. Em geral, serão um valor para tipos triviais e uma referência constante para tipos não triviais. Lembre-se de que o tipo de elemento de um mapa é uma TPair.
O fragmento de código a seguir é um exemplo de uma KeyFuncs personalizada:
MyCustomKeyFuncs.cpp
struct FMyStruct
{
// String which identifies our key
FString UniqueID;
// Some state which doesn't affect struct identity
float SomeFloat;
explicit FMyStruct(float InFloat)
: UniqueID (FGuid::NewGuid().ToString())
FMyStruct apresenta um identificador único, bem como alguns outros dados que não contribuem para sua identidade. GetTypeHash e operator== seriam inadequados aqui, pois operator== não deve ignorar dados do tipo para uso geral. No entanto, precisaria fazer isso ao mesmo tempo para manter a consistência com o comportamento de GetTypeHash, que só analisa o campo UniqueID.
Para criar uma KeyFuncs personalizada para FMyStruct, siga estas etapas:
Herda de
BaseKeyFuncs, pois define alguns tipos úteis, incluindoKeyInitTypeeElementInitType.BaseKeyFuncsusa dois parâmetros de modelo:O tipo de elemento do mapa.
Como em todos os mapas, o tipo de elemento é um
TPair, considerandoFMyStructcomoKeyTypee o parâmetro de modelo deTMyStructMapKeyFuncscomoValueType. OKeyFuncssubstituto é um modelo, para que você possa especificarValueTypeem cada mapa sem precisar definir uma novaKeyFuncstodas as vezes que quiser criar uma TMap com chave emFMyStruct.
O tipo da nossa chave.
O segundo argumento
BaseKeyFuncsé o tipo da chave, que não deve ser confundido comKeyTypede TPair, o campo Key das armazenamentos de elementos. Como esse mapa deve usarUniqueID(deFMyStruct) como sua chave,FStringserá usada aqui.
Define as três funções estático
KeyFuncsnecessárias.A primeira é GetSetKey, que retorna a chave para um determinado tipo de elemento. Como o tipo do elemento é
TPaire a chave éUniqueID,a função pode simplesmente retornarUniqueID.A segunda função estático é Matches, que pega as chaves de dois elementos recuperados por
GetSetKeye as compara para conferir se são equivalentes. ParaFString, o teste de equivalência padrão (operator==) não diferencia maiúsculas de minúsculas. Para substituir por uma pesquisa que diferencia maiúsculas de minúsculas, use a funçãoCompare()com a opção adequada de comparação de maiúsculas e minúsculas.A terceira função estática é
GetKeyHash, que usa uma chave extraída e retorna um valor de hash para ela. Como a funçãoMatchesdiferencia maiúsculas de minúsculas,GetKeyHashtambém precisa fazer isso. Uma função FCrc que diferencia maiúsculas de minúsculas calcula o valor hash da string de chave.
Agora que a estrutura é compatível com os comportamentos exigidos por TMap, você pode criar instâncias dela.
C++TMap< FMyStruct, int32, FDefaultSetAllocator, TMyStructMapKeyFuncs<int32> > MyMapToInt32; // Add some elements MyMapToInt32.Add(FMyStruct(3.14f), 5); MyMapToInt32.Add(FMyStruct(1.23f), 2);Neste exemplo, o alocador de conjunto padrão é especificado. Isso ocorre porque o parâmetro
KeyFuncsé o último e esse tipo deTMapexige isso.
Ao fornecer os próprios KeyFuncs, lembre-se de que o TMap presume que dois itens comparados como iguais com Matches também retornam o mesmo valor de GetKeyHash. Além disso, modificar a chave de um elemento de mapa existente de forma que altere os resultados de uma dessas funções é considerado um comportamento indefinido, pois invalida o hash interno do mapa. Essas regras também se aplicam a sobrecargas de operator== e GetKeyHash ao usar a KeyFuncs padrão.
Variado
As funções CountBytes e GetAllocatedSize estimam quanta memória a matriz interna está utilizando no momento. CountBytes usa um parâmetro FArchive, enquanto GetAllocatedSize não. Essas funções costumam ser usadas para relatórios de estatísticas.
A função Dump usa um FOutputDevice e grava algumas informações de implementação sobre o conteúdo do mapa. Essa função é normalmente usada para depuração.