TSet é semelhante a TMap e TMultiMap, mas com uma diferença importante: em vez de associar valores de dados a chaves independentes, uma TSet usa o próprio valor de dados como a chave por meio de uma função substituível que avalia o elemento. TSet é muito rápida (tempo constante) para adicionar, encontrar e remover elementos. Por padrão, TSet não é compatível com chaves duplicadas, mas esse comportamento pode ser ativado com um parâmetro de modelo.
TSet
TSet é uma classe de contêiner rápida que armazena elementos únicos em um contexto em que a ordem é irrelevante. Na maioria dos casos de uso, apenas um parâmetro, o tipo de elemento, é necessário. No entanto, TSet pode ser configurada com diferentes parâmetros de modelo para alterar seu comportamento e torná-la mais versátil. Você pode especificar um struct derivado com base em DefaultKeyFuncs para fornecer a funcionalidade de hash e permitir que várias chaves com o mesmo valor existam no set. Por fim, como nas outras classes de contêiner, você pode fornecer um alocador de memória personalizada para o armazenamento de dados.
Como TArray, TSet é um contêiner homogêneo, o que significa que todos os elementos são do mesmo tipo. TSet também é um tipo de valor e permite operações usuais de cópia, atribuição e destruidor, bem como uma forte propriedade dos elementos, que são destruídos quando TSet é. O tipo de chave também deve ser do tipo valor.
TSet usa hashes, o que significa que o parâmetro de modelo KeyFuncs, se fornecido, informa ao set como determinar a chave de um elemento, como comparar duas chaves em termos de igualdade, como fazer o hash da chave e se deve ou não permitir chaves duplicadas. Eles têm valores padrão que retornarão uma referência à chave. Use operator== para igualdade e a função GetTypeHash não membro para hash. Por padrão, o set não permitirá chaves duplicadas. Se o tipo de chave for compatível com essas funções, ele utilizável como uma chave definida sem a necessidade de fornecer uma KeyFuncs personalizada. Para escrever uma KeyFuncs personalizada, estenda o struct DefaultKeyFuncs.
Por fim, TSet pode usar um alocador opcional para controlar o comportamento de alocação de memória. Os alocadores padrão da Unreal Engine 4 (UE4) (como FHeapAllocator e TInlineAllocator) não podem ser usados como alocadores para TSet. Em vez disso, TSet usa alocadores de set, que definem quantos depósitos de hash o set deve usar e quais alocadores padrão da UE4 usar para armazenamento de elementos. Confira TSetAllocator para obter mais informações.
Diferente de TArray, a ordem relativa dos elementos de TSet 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 fiquem contíguos na memória. A estrutura de dados de apoio de um set é uma matriz esparsa, que é uma matriz que suporta eficientemente espaços entre seus elementos. À medida que os elementos são removidos do set, vão aparecendo lacunas na matriz esparsa. Adicionar novos elementos à matriz pode preencher essas lacunas. No entanto, mesmo que TSet não embaralhe elementos para preencher lacunas, os ponteiros para elementos podem ser invalidados, pois todo o armazenamento pode ser realocado quando estiver cheio e novos elementos forem adicionados.
TSet (como muitos contêineres da Unreal Engine) assume que o tipo do 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.
Como criar e preencher um set
Você pode criar um TSet assim:
TSet<FString> FruitSet;Isso cria um TSet vazio que conterá dados de FString. TSet compara elementos diretamente com operator==, os armazena usando GetTypeHash e usa o alocador de heap padrão. Nenhuma memória foi alocada até agora.
A maneira padrão de popular um set é usar a função Add e fornecer uma chave (elemento):
FruitSet.Add(TEXT("Banana"));
FruitSet.Add(TEXT("Grapefruit"));
FruitSet.Add(TEXT("Pineapple"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple" ]Embora os elementos estejam listados aqui na ordem de inserção, não há garantia quanto à ordem real deles na memória. Para um novo set, é provável que os elementos 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.
Como este set usa o alocador padrão, as chaves são únicas. Este é o resultado da tentativa de adicionar uma chave duplicada:
FruitSet.Add(TEXT("Pear"));
FruitSet.Add(TEXT("Banana"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear" ]
// Note: Only one banana entry.O conjunto agora contém quatro elementos. Pear elevou a contagem de três para quatro, mas a nova "Banana" não alterou o número de elementos no set porque substituiu a antiga entrada "Banana".
Como em TArray, também podemos usar Emplace em vez de Add para evitar a criação de temporários ao inserir no conjunto:
FruitSet.Emplace(TEXT("Orange"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange" ]Aqui, o argumento é passado diretamente ao constructor do tipo chave. Isso evita a criação de uma FString temporária para o valor. Diferente de TArray, só é possível colocar elementos em um conjunto com construtores de argumento único.
Também é possível inserir todos os elementos de outro set usando a função Append para mesclá-los:
TSet<FString> FruitSet2;
FruitSet2.Emplace(TEXT("Kiwi"));
FruitSet2.Emplace(TEXT("Melon"));
FruitSet2.Emplace(TEXT("Mango"));
FruitSet2.Emplace(TEXT("Orange"));
FruitSet.Append(FruitSet2);
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange", "Kiwi", "Melon", "Mango" ]No exemplo acima, o conjunto resultante equivale a usar Add ou Emplace para adicionar os elementos individualmente. Chaves duplicadas do set de origem substituirão suas correspondentes no alvo.
Como editar TSets UPROPERTY
Se você marcar TSet com o macro UPROPERTY e uma das palavras-chave editáveis (EditAnywhere, EditDefaultsOnly ou EditInstanceOnly), poderá adicionar e editar elementos no Unreal Editor.
UPROPERTY(Category = SetExample, EditAnywhere)
TSet<FString> FruitSet;Iteração
A iteração em TSets é semelhante a TArrays. Você pode usar a funcionalidade ranged-for do C++:
for (auto& Elem : FruitSet)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT(" \"%s\"\n"),
*Elem
)
);
}
// Output:
Também é possível criar iteradores com as funções CreateIterator e CreateConstIterators. CreateIterator retornará um iterador com acesso de leitura e gravação, enquanto CreateConstIterator retornará 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 conjunto de exemplo fruta usando iteradores ficaria assim:
for (auto It = FruitSet.CreateConstIterator(); It; ++It)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%s)\n"),
*It
)
);
}Consultas
Para descobrir quantos elementos existem no conjunto, chame a função Num.
int32 Count = FruitSet.Num();
// Count == 8Para determinar se um set contém um elemento específico, faça a chamada da função Contains da seguinte maneira:
bool bHasBanana = FruitSet.Contains(TEXT("Banana"));
bool bHasLemon = FruitSet.Contains(TEXT("Lemon"));
// bHasBanana == true
// bHasLemon == falseUse o struct FSetElementId para encontrar o índice de uma chave no conjunto. Você pode usar o índice com operator[] para recuperar o elemento. Chamar operator[] em um set não constante retornará uma referência não constante, e chamá-lo em um set constante retornará uma referência constante.
FSetElementId BananaIndex = FruitSet.Index(TEXT("Banana"));
// BananaIndex is a value between 0 and (FruitSet.Num() - 1)
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT(" \"%s\"\n"),
*FruitSet[BananaIndex]
)
);
// Prints "Banana"
Se não tiver certeza se o conjunto contém uma chave, verifique com a função Contains e 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 se o set contiver a chave, ou um ponteiro nulo se não contiver. Se você chamar Find em um constante set, o ponteiro retornado também será constante.
FString* PtrBanana = FruitSet.Find(TEXT("Banana"));
FString* PtrLemon = FruitSet.Find(TEXT("Lemon"));
// *PtrBanana == "Banana"
// PtrLemon == nullptrA função Array retorna uma TArray preenchida com uma cópia de todos os elementos de TSet. A matriz que você passa será esvaziada no início da operação. Portanto, o número de elementos resultante será sempre igual ao número de elementos no set:
TArray<FString> FruitArray = FruitSet.Array();
// FruitArray == [ "Banana","Grapefruit","Pineapple","Pear","Orange","Kiwi","Melon","Mango" ] (order may vary)Remoção
Elementos podem ser removidos por índice com a função Remove, embora isso seja recomendado apenas para iterar entre os elementos. A função Remove retorna o número de elementos removidos e será 0 se a chave fornecida não estiver contida no set. Se uma TSet permitir chaves duplicadas, Remove removerá todos os elementos correspondentes.
FruitSet.Remove(0);
// FruitSet == [ "Grapefruit","Pineapple","Pear","Orange","Kiwi","Melon","Mango" ]Remover elementos pode deixar buracos na estrutura de dados, que você pode ver ao visualizar o set na janela de observação do Visual Studio, mas foram omitidos aqui para maior clareza.
int32 RemovedAmountPineapple = FruitSet.Remove(TEXT("Pineapple"));
// RemovedAmountPineapple == 1
// FruitSet == [ "Grapefruit","Pear","Orange","Kiwi","Melon","Mango" ]
FString RemovedAmountLemon = FruitSet.Remove(TEXT("Lemon"));
// RemovedAmountLemon == 0Por fim, você pode remover todos os elementos do set com as funções Empty ou Reset.
TSet<FString> FruitSetCopy = FruitSet;
// FruitSetCopy == [ "Grapefruit","Pear","Orange","Kiwi","Melon","Mango" ]
FruitSetCopy.Empty();
// FruitSetCopy == []Empty e Reset são coisas parecidas, mas Empty pode usar um parâmetro para indicar quanta margem deve ser deixada no conjunto, enquanto Reset sempre deixa a maior margem possível.
Classificação
Uma TSet pode ser classificada. Após a classificação, a iteração sobre o set apresentará os elementos na ordem classificada, mas esse comportamento só é garantido até a próxima vez que você modificar o set. A classificação é instável. Portanto, os elementos equivalentes em um set que permite chaves duplicadas podem aparecer em qualquer ordem.
A função Sort usa um predicado binário que especifica a ordem de classificação da seguinte maneira:
FruitSet.Sort([](const FString& A, const FString& B) {
return A > B; // sort by reverse-alphabetical order
});
// FruitSet == [ "Pear", "Orange", "Melon", "Mango", "Kiwi", "Grapefruit" ] (order is temporarily guaranteed)
FruitSet.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len(); // sort strings by length, shortest to longest
});
// FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ] (order is temporarily guaranteed)Operadores
Como TArray, TSet é um tipo de valor regular e, como tal, pode ser copiado com o constructor padrão ou o operador de atribuição. Sets são proprietários dos seus elementos, portanto, copiar um set é profundo; o novo set terá sua própria cópia dos elementos.
TSet<int32, FString> NewSet = FruitSet;
NewSet.Add(TEXT("Apple"));
NewSet.Remove(TEXT("Pear"));
// FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ]
// NewSet == [ "Kiwi", "Melon", "Mango", "Orange", "Grapefruit", "Apple" ]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 set 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 set que espera ser preenchido imediatamente com o mesmo número de elementos ou menos.
TSet não fornece uma maneira de verificar quantos elementos estão pré-alocados como a função Max em TArray.
O código a seguir remove todos os elementos do set sem desalocar memória, resultando na criação do slack:
FruitSet.Reset();
// FruitSet == [ <invalid>, <invalid>, <invalid>, <invalid>, <invalid>, <invalid> ]Para criar o slack diretamente, como pré-alocar memória antes de adicionar elementos, use a função Reserve.
FruitSet.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{
FruitSet.Add(FString::Printf(TEXT("Fruit%d"), i));
}
// FruitSet == [ "Fruit9", "Fruit8", "Fruit7" ... "Fruit2", "Fruit1", "Fruit0" ]A pré-alocação do slack fez com que os novos elementos fossem adicionados na ordem inversa. Ao contrário das matrizes, os sets não tentam manter a ordem dos elementos, e o código que lida com sets não deve esperar que a ordem dos elementos seja estável ou previsível.
Para remover toda a folga de um TSet, 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.
// Remove every other element from the set.
for (int32 i = 0; i < 10; i += 2)
{
FruitSet.Remove(FSetElementId::FromInteger(i));
}
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0", <invalid> ]
FruitSet.Shrink();
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0" ]Shrink removeu apenas um elemento inválido do código acima, pois havia apenas um elemento vazio no final. Para remover todo o slack, a função Compact ou CompactStable deve ser chamada primeiro, para que os espaços vazios sejam agrupados em preparação para Shrink. Como o próprio nome sugere, CompactStable mantém a ordem dos elementos enquanto consolida elementos vazios.
FruitSet.CompactStable();
// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0", <invalid>, <invalid>, <invalid>, <invalid> ]
FruitSet.Shrink();
// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0" ]DefaultKeyFuncs
Desde que um tipo tenha um operator== e uma sobrecarga de GetTypeHash não membro, TSet poderá usá-lo, pois o tipo é o elemento e a chave. No entanto, pode ser útil usar tipos como chaves onde não é desejável sobrecarregar essas funções. Nesses casos, você pode fornecer suas próprias DefaultKeyFuncs personalizadas. Para criar KeyFuncs para o seu tipo de chave, você deve definir duas typedefs e três funções estáticas, como segue:
KeyInitType: Tipo usado para passar chaves. Geralmente é obtido a partir do parâmetro de modelo ElementType.ElementInitType: Tipo usado para passar elementos. Também é obtido do parâmetro de modelo ElementType e, portanto, é idêntico a KeyInitType.KeyInitType GetSetKey(ElementInitType Element): retorna a chave de um elemento. Para sets, é o elemento em si.bool Matches(KeyInitType A, KeyInitType B): retornatrueseAeBforem equivalentes,falsecaso contrário.uint32 GetKeyHash(KeyInitType Key): retorna o valor hash deKey.
KeyInitType e ElementInitType são typedefs para a convenção normal de passagem do tipo de chave/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 set também é o tipo de chave. Por isso, DefaultKeyFuncs usa apenas um parâmetro de modelo, ElementType, para definir os dois.
TSet presume que dois itens que são comparados usando partidas (em DefaultKeyFuncs) também retornarão o mesmo valor de GetKeyHash (em KeyFuncs).
Não modifique a chave de um elemento existente de forma a alterar os resultados dessas funções, pois isso invalidará o hash interno do set. Essas regras também se aplicam às sobrecargas de operator== e GetKeyHash ao usar a implementação padrão de DefaultKeyFuncs.
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 set. Também há uma função DumpHashElements que lista os elementos de todas as entradas de hash. Essas funções geralmente são usadas para depuração.