#c #pointers #memory-management #memory-leaks
#c #указатели #управление памятью #утечки памяти
Вопрос:
Недавно я заново изучал C , разрабатывая игру на движке Unreal engine. Прошло около 3 лет с тех пор, как я коснулся C , и с тех пор я в основном использую Java.
Из-за различий между Java и c я уже могу сказать, что существуют разные рекомендации для похожих концепций.
У меня есть 2 таких метода.
void UMarchingSquares::Generate(std::map<Vector2, int> automata) {
std::map<Vector2,ControlNode*> controlNodes = getControlNodes(automata);
}
std::map<Vector2,ControlNode*> UMarchingSquares::getControlNodes(std::map<Vector2, int> automata) {
std::map<Vector2,ControlNode*> controlNodes = std::map<Vector2, ControlNode*>();
for(pair<Vector2,int> pair : automata) {
Vector2 pos = pair.first;
ControlNode node = ControlNode(pos,pair.second);
controlNodes[pos] = amp;node;
}
return controlNodes;
}
Я, вероятно, нарушаю несколько различных рекомендаций C , но есть один, который я действительно хочу уточнить в одной конкретной области.
Я инициализирую ControlNode
объект в getControlNodes()
методе for loop
. Теперь я знаю, что делать это таким образом плохо, потому что я сохраняю указатель на локальную переменную, которая затем выходит из области видимости при каждой итерации цикла. Я бы предпочел хранить указатели вместо фактического управляющего узла (хотя я могу быть убежден в обратном, поскольку управляющий узел содержит позицию [2 числа с плавающей запятой], материал [1 целое число] и два других объекта, которые имеют собственную позицию и материал.)
Каков наилучший способ создать указатель на нелокальную переменную? Я знаю, что могу просто использовать « new ControlNode()
«, но из того, что я знаю, это оказывается довольно дорогим вызовом и требует очистки (которая также может быть дорогостоящей). Я собираюсь вызывать эту часть кода довольно часто, поэтому я хотел бы, чтобы это было эффективно.
Спасибо!
Комментарии:
1. Зачем вам нужны указатели?
2. Если вы забудете Java, ваш код станет проще до такой степени, что ваш вопрос может стать спорным (в зависимости от того, почему вы считаете, что вам нужны указатели). Пример:
std::map<Vector2,ControlNode> controlNodes;
Нет необходимости в избыточном синтаксисе инициализации.3. Предполагая, что вы придерживаетесь указателей, тогда разумный указатель — это правильный путь,
std::unique_ptr<ControlNode>
. Возможно, вы также захотите взглянуть на семантику перемещения для этого дополнительного повышения эффективности.
Ответ №1:
C сильно изменился за последние несколько лет, чтобы облегчить жизнь тем, кто его использует.
Глядя на ваш код, возникает много вопросов:
- Почему значение вашей карты является необработанным указателем на ControlNode вместо ControlNode по значению или unique_ptr к нему
- Почему в вашем цикле for вы пишете явный тип пары (который отличается от итератора),
auto
может помочь вам иметь меньше копий
Поскольку ваш вопрос касается первого, я проигнорирую второй.
Учитывая это, у вас есть 3 способа исправления кода:
std::map<Vector2,ControlNode> getControlNodes(std::map<Vector2, int> automata) {
auto controlNodes = std::map<Vector2, ControlNode>{};
for(auto amp;amp;pair : automata) {
auto amp;amp;pos = pair.first;
auto node = ControlNode(pos,pair.second);
controlNodes[pos] = std::move(node);
}
return controlNodes;
}
В этом коде вы можете видеть, что *
был удален с карты. Это означает, что вместо карты перемещается право собственности на ControlNode. (Также обратите внимание на std::move
) Это было бы похоже на то, как у вас хранится значение int в map, которое вводится в качестве аргумента.
Однако, если вам требуется выделение памяти, поскольку вы будете перемещать ее, а адрес должен быть стабильным, std::unique_ptr
это хорошее решение.
std::map<Vector2,std::unique_ptr<ControlNode>> getControlNodes(std::map<Vector2, int> automata) {
auto controlNodes = std::map<Vector2, std::unique_ptr<ControlNode>>{};
for(auto amp;amp;pair : automata) {
auto amp;amp;pos = pair.first;
auto node = std::make_unique<ControlNode>(pos,pair.second);
controlNodes[pos] = std::move(node);
}
return controlNodes;
}
Как вы можете видеть, этот код очень похож на предыдущий, я заменил тип в map и изменил конструкцию ControlNode на std::make_unique
. Следовательно, у вас есть unique_ptr, содержащий право собственности на выделенную память (и пока у вас есть unique_ptr, все остается в силе).
Третье решение следует использовать, только если вы не можете изменить подпись, и оно считается плохой практикой в C , поскольку оно передает право собственности через необработанные указатели. Теперь ваш вызывающий абонент отвечает за явную очистку памяти, поскольку в C нет сборки мусора.
std::map<Vector2,ControlNode*> getControlNodes(std::map<Vector2, int> automata) {
auto controlNodes = std::map<Vector2, ControlNode*>{};
for(auto amp;amp;pair : automata) {
auto amp;amp;pos = pair.first;
auto node = new ControlNode(pos,pair.second);
controlNodes[pos] = node;
}
return controlNodes;
}
PS: Я добавил в код немного auto, чтобы сделать изменения между фрагментами минимальными.
Комментарии:
1. Да, те первые две проблемы, о которых вы упомянули, связаны с тем, что мои старые курсы C , которые я посещал, никогда не касались уникальных указателей, и синтаксис «auto», который они просто ожидали, что мы узнаем, поможет ускорить наши программы. Второй вариант, похоже, может быть лучшим вариантом, поскольку в каждом из переданных автоматов содержится около 2500 элементов, и это будет вызываться довольно часто. Спасибо!
Ответ №2:
Используйте вектор управляющих узлов для хранения. Всякий раз, когда вам нужен новый управляющий узел, добавьте единицу к этому вектору. Вместо использования указателя используйте итератор для этого вектора. Убедитесь, что вы заранее зарезервировали достаточно слотов в этом векторе, иначе ваши итераторы будут признаны недействительными.