1
votes

Conversion d'un conteneur std :: vector en un std :: set à l'aide de std :: transform

Plus précisément, j'ai un vecteur d'une certaine structure

typedef struct VkExtensionProperties {
    char        extensionName[VK_MAX_EXTENSION_NAME_SIZE];
    uint32_t    specVersion;
} VkExtensionProperties;

someStructVariable.extensionName renvoie une chaîne.

Et je veux créer un ensemble de extensionName , quelque chose comme ceci std :: set .

Le processus est assez simple lorsqu'il est fait avec un pour les boucles mais je souhaite utiliser à la place std :: transform de .


std: : transform a quatre paramètres.

1,2 . Première plage (pour transformer la première plage de et en)

3 . Deuxième plage / inserteur (pour transformer la deuxième plage)

4 . Une fonction


C'est ce que j'ai jusqu'à présent

auto lambdaFn = 
    [](SomeStruct x) -> const char* { return x.extensionName; };

 std::transform(availableExtensions.begin(),
                availableExtensions.end(),
                std::inserter(xs, xs.begin()),
                lambdaFn);

car il n'y a pas de "contexte approprié" pour std :: back_inserter dans std :: set J'utilise std :: inserter (xs, xs.begin ()) .


Le problème est que j'essaye de renvoyer la pile mem dans ma fonction lambda. Alors, comment contourner ce problème?

Curieusement, si je supprime return de la fonction, cela fonctionne exactement comme je m'y attendais! Mais je ne comprends pas pourquoi et cela fait peur des répercussions futures.


EDIT:

J'utilise plusieurs structures à la place de SomeStruct code > comme VkExtensionProperties défini dans vulkan_core

std::vector<SomeStruct> extensions = getThoseExtensions();

De Spécifications Khronos


11 commentaires

Quel " stack mem " renvoyez-vous? Je suppose que SomeStruct ne possède pas la chaîne de caractères pointée par extensionName , non? Que signifie " si je supprime return "? Si vous supprimez le mot-clé return , votre lambda aura un comportement indéfini car il ne retournera pas de valeur lorsqu'il prétend retourner non-void.


veuillez fournir un exemple reproductible minimal . Btw std :: set a un constructeur qui prend deux itérateurs vers une plage d'éléments à insérer. Vous n'avez pas besoin de std :: transform


pas le problème: le nom functionPointer est un peu trompeur. Il n'y a pas de pointeur de fonction dans votre code. functionPointer est un lambda


@ formerlyknownas_463035818 Encore aurait besoin d'un itérateur de transformation si OP veut extraire le membre de chaque élément.


Votre SomeStruct est passé par valeur à functionPointer , ce qui pourrait potentiellement entraîner des problèmes. Devrait voir la définition de SomeStruct .


@walnut oh oui, ma faute. J'ai manqué ça


@super J'utilise plusieurs structures différentes à la place de SomeStruct , l'une est VkExtensionProperties


@walnut J'ai supposé qu'à cause de cet avertissement sonore, j'en recevais "l'adresse de la mémoire de la pile associée à la variable locale renvoyée". Par remove return , je voulais en fait supprimer le mot-clé return de la fonction lambda.


@atis Votre question dit que extensionName est une chaîne et votre lien dit que c'est un char * , et vous dites également que vous utilisez une structure différente. Alors s'il vous plaît clarifiez. Le type de extensionName est-il toujours char * ou pourrait-il être potentiellement des choses différentes comme std :: string etc.?


@atis La suppression du mot-clé return entraîne un comportement indéfini et il aurait dû y avoir également un avertissement par clang. L'avertissement donne l'impression que SomeStruct possède la mémoire vers laquelle pointe extensionName . Dans ce cas, votre approche de prendre (non-propriétaire) const char * comme type d'élément du set est probablement déjà en elle-même cassée (en particulier si vous ne faites pas sûr que le std :: vector survit au std :: set ). Voir par exemple réponse d'AlanBirtles pour avoir corrigé ce problème.


@atis D'autres ont mentionné la durée de vie du vecteur - mais même si le vecteur survit à l'ensemble, sachez que l'ajout d'éléments à un vecteur est autorisé à déplacer le vecteur ailleurs en mémoire, ce qui invaliderait tout les char * dans l'ensemble! Juste une autre raison std::set est une bonne idée.


3 Réponses :


0
votes

Il y a deux choses que vous pouvez faire ici.

  1. Si vous possédez la définition de SomeStruct , il est préférable de remplacer ce membre par std :: string .
  2. En bref, voyez si vous lambda pouvez prendre le paramètre by-ref const auto & obj . Cela ne créera pas de copie et ne pointera pas vers l'objet du conteneur. Cependant, j'ai toujours peur de cette solution car cela sent le mauvais design de classe où la propriété et la durée de vie des membres sont ambiguës.

0 commentaires

6
votes

Vous ne pouvez probablement pas créer un ensemble de char * à moins que toutes les instances de extensionName avec la même valeur pointent vers le même tableau de caractères (cela stockerait des pointeurs uniques à la place de valeurs uniques). Si vous utilisez à la place std::set<:string> , cela fonctionnera à la fois et ne stockera que des valeurs uniques et résoudra votre problème de durée de vie variable comme std :: string prend soin de se copier (ou déplacer) pour vous si nécessaire:

auto lambdaFn = 
    [](const SomeStruct& x) { return std::string(x.extensionName); };
std::set<std::string> xs;
std::transform(availableExtensions.begin(),
                availableExtensions.end(),
                std::inserter(xs, xs.begin()),
                lambdaFn);


0 commentaires

1
votes

Une façon de faire ce que vous voulez est d'utiliser le lambda suivant

const char* str = "Hello World!";

Le problème avec ceci est que vous enregistrez des pointeurs vers la mémoire appartenant à quelqu'un d'autre (ces chaînes). Lorsqu'ils sont détruits (cela semble être le cas à la fin de la fonction), le pointeur sera suspendu. Vous pouvez contourner cela en allouant de la mémoire dans votre lambda et en copiant les données:

auto lambda = [](const SomeStruct & x) -> const char* {
    char* c = new char[x.extensions.length()+1]; 
    std::strcpy(c, x.extensions.data()); 
    return c;
}

Mais alors vous devez faire la gestion de la mémoire vous-même (c'est-à-dire n'oubliez pas de libérer ces const char * ). Et c'est une mauvaise idée. Vous devriez probablement reconsidérer ce que vous faites. Pourquoi utilisez-vous const char * ici et non std :: string ?

N'oubliez pas que l'utilisation typique de const char * est de sauvegarder les littéraux de chaîne i C-code, c'est-à-dire le code

auto lambda = [](const SomeStruct& x) -> const char* { return x.extensions.data();};

crée un tableau char de taille suffisante dans la section statique de la mémoire, l'initialise avec la chaîne (une constante de temps de compilation) puis enregistre un pointeur vers celui dans str code >. C'est aussi pourquoi cela doit être un const char * car un autre pointeur faisant référence à un littéral de chaîne égal peut (ou non) pointer exactement le même tableau de caractères et vous ne voulez pas activer le changement là. Alors n'utilisez pas simplement const char * car vous voyez des chaînes en C enregistrées dans ces const char * sans que personne n'ait besoin de les libérer plus tard.

p >


0 commentaires