Преобразование контейнера std :: vector в std :: set с использованием std :: transform - PullRequest
1 голос
/ 13 января 2020

Более конкретно, у меня есть вектор некоторой структуры

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

, где someStructVariable.extensionName возвращает строку.

И я хочу создать набор extensionName, что-то вроде этого std::set<const char*>.

Процесс довольно прост, когда выполняется несколько циклов for, но я хочу использовать вместо него std::transform из <algorithm>.


std::transform имеет четыре параметры.

1,2 . Первый диапазон (для преобразования первого диапазона из и в)

3 . Второй диапазон / устройство вставки (для преобразования второго диапазона)

4 . Функция


Это то, что у меня пока есть

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

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

, потому что нет "правильного контекста" для std::back_inserter в std::set, который я использую std::inserter(xs, xs.begin()).


Проблема в том, что я пытаюсь вернуть стековую память в моей лямбда-функции. Итак, как мне обойти эту проблему?

Как ни странно, если я удаляю return из функции, она работает так, как я и ожидал! Но я не понимаю, почему, и это вселяет страх перед будущими последствиями.


РЕДАКТИРОВАТЬ:

Я использую несколько структур вместо SomeStruct как VkExtensionProperties, определенный в vulkan_core

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

От Спецификации Khronos

Ответы [ 3 ]

6 голосов
/ 13 января 2020

Вы, вероятно, не сможете создать набор char *, если все экземпляры extensionName с одинаковым значением не указывают на один и тот же массив символов (он будет хранить уникальные указатели вместо уникальных значений). Если вместо этого вы используете std::set<std::string>, это будет работать и хранить только уникальные значения и решать вашу проблему времени жизни переменной, так как std::string заботится о копировании (или перемещении) самого себя в случае необходимости:

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);
1 голос
/ 13 января 2020

Одним из способов сделать то, что вы хотите, является следующая лямбда

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

Проблема в том, что вы сохраняете указатели в памяти, принадлежащей кому-то другому (эти строки). Когда они уничтожены (это, кажется, имеет место в конце функции), указатель будет зависать. Вы можете обойти это, выделив память в своей лямбде и скопировав данные:

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

Но тогда вам придется самостоятельно управлять памятью (то есть не забудьте освободить эти const char*). И это плохая идея. Вам, вероятно, следует пересмотреть то, что вы делаете. Почему вы используете const char* здесь, а не std:: string?

Пожалуйста, помните, что типичное использование const char* - это сохранение строковых литералов в i C -коде, т. Е. Код

const char* str = "Hello World!";

создает массив char достаточного размера в секция памяти stati c, инициализирует ее строкой (постоянной времени компиляции), а затем сохраняет указатель на нее в str. По этой же причине это должен быть const char*, поскольку другой указатель, ссылающийся на одинаковый строковый литерал, может (или не может) указывать на точно такой же массив символов, и вы не хотите разрешать изменение там. Так что не просто используйте const char*, потому что вы видите строки в C, сохраненные в этих const char*, без необходимости их дальнейшего освобождения.

0 голосов
/ 13 января 2020

Здесь вы можете сделать несколько вещей:

  1. Если вы владеете определением SomeStruct, лучше, если вы измените этот член на std::string.
  2. Если не считать этого, посмотрите, может ли ваша лямбда принять параметр by-ref const auto& obj. Это не создаст копию и не укажет на объект, который имеет контейнер. Тем не менее, я все еще боюсь этого решения, так как оно пахнет плохим дизайном класса, где владение и срок жизни членов неоднозначны.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...