Передача вновь выделенных данных непосредственно в функцию

Изучая разные языки, я часто видел объекты, выделяемые на лету, чаще всего в Java и C#, например:

functionCall(new className(initializers));

Я понимаю, что это совершенно законно для языков с управлением памятью, но можно ли использовать этот метод в C++, не вызывая утечки памяти?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
0
265
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Да, если вы освобождаете память внутри функции. Но это ни в коем случае не лучшая практика для C++.

Тогда что было бы лучше?

Cristián Romo 29.12.2008 23:21

Выделить, вызвать функцию, освободить. Дополнительная переменная, необходимая для указания на выделенный объект.

Otávio Décio 29.12.2008 23:26

Как обычно, лучшая практика - RAII. Если у вас есть вызовы new, видимые в вашем пользовательском коде, велика вероятность, что вы делаете это неправильно. По крайней мере, оберните выделение в умный указатель. Я просто добавил ответ, демонстрирующий это.

jalf 30.12.2008 00:03

Если U может изменить код функции, поместите туда новый и передайте инициализаторы. Сохраняйте переменные как можно более локальными. Если у U нет доступа к коду, это БУДЕТ вызывать утечку памяти. Создайте указатель на новый объект, на одну строку вверх и передайте его ..

baash05 30.12.2008 00:06

Кстати ... Это лучше всего и в C#, функции в вызовах функций всегда плохая идея для отладки. По одной идее в строке. :)

baash05 30.12.2008 00:06

По-разному.

Это передает "владение" памятью functionCAll (). Ему нужно будет либо освободить объект, либо сохранить указатель, чтобы его можно было освободить позже. Подобная передача прав собственности на необработанные указатели - один из самых простых способов встроить в код проблемы с памятью - либо утечки, либо двойные удаления.

И передача права собственности, как это, обычно является грязной сделкой, потому что это не очевидно, если взглянуть на необработанный указатель, которому он принадлежит.

jalf 29.12.2008 23:46

Это будет работать для объектов, созданных в стеке, но не для обычного указателя в C++.

Может быть, автоматический указатель справится с этим, но я еще не слишком много с ними связывал, чтобы знать.

Обычный auto_ptr не будет (сам вызов функции создает копию auto_ptr, что приводит к передаче права собственности), но const auto_ptr должен (если я правильно понял книгу Джозаттиса) делать то, что вы ожидаете.

Harper Shelby 29.12.2008 23:24

В общем, нет, если только вы не хочу для утечки памяти. На самом деле, в большинстве случаев это не сработает, поскольку результат

new T();

в C++ - это T *, а не T (в C# new T () возвращает T).

Это безопасно, если вызываемая функция имеет семантику принятия права собственности. Я не помню, когда мне это было нужно, поэтому считаю это необычным.

Если функция работает таким образом, она должна принимать свой аргумент как объект интеллектуального указателя, чтобы намерение было чистым; т.е.

void functionCall(std::auto_ptr<className> ptr);

скорее, чем

void functionCall(className* ptr);

Это делает передачу права собственности явной, и вызывающая функция будет избавляться от памяти, на которую указывает ptr, когда выполнение функции выходит за рамки.

Ответ принят как подходящий

Ваш код действителен (при условии, что functionCall () действительно гарантирует, что указатель будет удален), но он хрупкий и вызовет тревогу в головах большинства программистов на C++.

С вашим кодом есть несколько проблем:

  • Прежде всего, кому принадлежит указатель? Кто отвечает за его освобождение? Вызывающий код не может этого сделать, потому что вы не храните указатель. Это означает, что вызываемая функция должна это делать, но это не ясно для тех, кто смотрит на эту функцию. Точно так же, если я вызываю код откуда-то еще, я, конечно, не ожидаю, что функция вызовет delete для указателя, который я ей передал!
  • Если мы немного усложним ваш пример, это может привести к утечке памяти, даже если вызываемая функция вызывает удаление. Скажем, это выглядит так: functionCall(new className(initializers), new className(initializers)); Представьте, что первый выделен успешно, но второй выдает исключение (возможно, не хватает памяти, или, может быть, конструктор класса сгенерировал исключение). Тогда functionCall никогда не вызывается, а не могу освобождает память.

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

className* p = new className(initializers);
functionCall(p);
delete p;

Но это по-прежнему беспорядок. Что, если functionCall выдаст исключение? Тогда p не будет удален. Если мы не добавим попытку / уловку во всем, но черт возьми, это грязно. Что, если функция станет немного сложнее и может вернуться после functionCall, но до удаления? Упс, утечка памяти. Невозможно поддерживать. Плохой код.

Итак, одно из хороших решений - использовать умный указатель:

boost::shared_ptr<className> p = boost::shared_ptr<className>(new className(initializers));
functionCall(p);

Теперь вопрос о владении памятью. shared_ptr владеет памятью и гарантирует, что она будет освобождена. Конечно, мы могли бы использовать std::auto_ptr, но shared_ptr реализует семантику, которую вы обычно ожидаете.

Обратите внимание, что я по-прежнему выделил память в отдельной строке, потому что проблема с несколькими выделениями в одной строке при вызове функции все еще существует. Один из них еще может выкинуть, значит, у вас утечка памяти.

Умные указатели - это, как правило, абсолютный минимум, необходимый для управления памятью. Но часто решение красивый состоит в том, чтобы написать свой собственный класс RAII.

className должен быть размещен в стеке, и в его конструкторе укажите, какие распределения с new необходимы. И в своем деструкторе он должен освободить эту память. Таким образом, вы гарантируете, что утечки памяти не произойдет, и вы можете сделать вызов функции таким простым:

functionCall(className(initializers));

Стандартная библиотека C++ работает так. std::vector - один из примеров. Вы бы никогда не разместили вектор с помощью new. Вы размещаете его в стеке, и позволяете ему заниматься распределением памяти внутри.

Избегайте создания нулевого ptr и его присвоения, вы не гарантируете, что T p = T (x) будет скомпилирован оптимизированным как T p (x); т.е. boost :: shared_ptr <className> p (новое имя класса (инициализаторы)); В этом конкретном случае вы также можете рассматривать std :: auto_ptr как безопасный для памяти, без необходимости совместного использования.

Greg Domjan 31.12.2008 06:39

В C++ мы бы не создавали память таким образом динамически. Вместо этого вы должны создать временный объект стека.

Вам нужно только создать объект кучи через new, если вы хотите, чтобы время жизни объекта было больше, чем время вызова функции. В этом случае вы можете использовать new в сочетании с интеллектуальным указателем (см. Другие ответы для примера).

// No need for new or memory management just do this
functionCall(className(initializers));

// This assumes you can change the functionCall to somthing like this.
functionCall(className const& param)
{
    << Do Stuff >>
}

Если вы хотите передать неконстантную ссылку, сделайте это так:

calssName tmp(initializers);
functionCall(tmp);

functionCall(className& param)
{
    << Do Stuff >>
}

Другие вопросы по теме