Когда следует использовать новое ключевое слово в C ++?

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

1) С новым ключевым словом …

MyClass* myClass = new MyClass(); myClass->MyField = "Hello world!"; 

2) Без нового ключевого слова …

 MyClass myClass; myClass.MyField = "Hello world!"; 

С точки зрения реализации они не кажутся разными (но я уверен, что они есть) … Однако мой основной язык – это C #, и, конечно, первый метод – это то, к чему я привык.

Трудность, похоже, заключается в том, что метод 1 сложнее использовать с classами std C ++.

Какой метод я должен использовать?

Обновление 1:

Недавно я использовал новое ключевое слово для памяти кучи (или свободного хранилища ) для большого массива, который вышел из области действия (т.е. возвращался из функции). Где раньше я использовал стек, из-за которого половина элементов была повреждена за пределами видимости, переход на использование кучи гарантировал, что элементы были в такте. Ура!

Обновление 2:

Недавно мой друг сказал мне, что есть простое правило для использования new ключевого слова; каждый раз, когда вы вводите new , введите delete .

 Foobar *foobar = new Foobar(); delete foobar; // TODO: Move this to the right place. 

Это помогает предотвратить утечку памяти, поскольку вам всегда нужно удалять что-либо (т. Е. Когда вы вырезаете и вставляете его либо деструктору, либо иным образом).

Метод 1 (с использованием new )

  • Выделяет память для объекта в свободном хранилище (это часто то же самое, что и куча )
  • Требуется явно delete объект позже. (Если вы не удалите его, вы можете создать утечку памяти)
  • Память остается выделенной до тех пор, пока вы ее не delete . (т. е. вы можете return объект, созданный с помощью new )
  • Пример в вопросе будет утечка памяти, если указатель не delete d; и он всегда должен быть удален , независимо от того, какой путь управления принят, или если выбраны исключения.

Метод 2 (не используется new )

  • Выделяет память для объекта в стеке (куда идут все локальные переменные) В стеке обычно меньше памяти; если вы выделяете слишком много объектов, вы рискуете переполнением стека.
  • Вам не нужно будет delete его позже.
  • Память больше не выделяется, когда она выходит за frameworks. (т. е. вы не должны return указатель на объект в стеке)

Что касается использования; вы выбираете метод, который лучше всего подходит для вас, учитывая приведенные выше ограничения.

Некоторые простые случаи:

  • Если вы не хотите беспокоиться о вызове delete (и о возможном утечке памяти ), вы не должны использовать new .
  • Если вы хотите вернуть указатель на свой объект из функции, вы должны использовать new

Между ними существует важное различие.

Все, что не назначено new ведет себя так же, как типы значений в C # (и люди часто говорят, что эти объекты выделены в стеке, что, вероятно, является наиболее распространенным / очевидным случаем, но не всегда верно. Точнее, объекты, выделенные без использования new , длительность автоматического хранения. Все выделенные new выделяются в куче, и указатель на нее возвращается точно так же, как ссылочные типы в C #.

Все, что выделяется в стеке, должно иметь постоянный размер, определенный во время компиляции (компилятор должен правильно установить указатель стека или если объект является членом другого classа, он должен отрегулировать размер этого другого classа) , Вот почему массивы в C # являются ссылочными типами. Они должны быть, потому что со ссылочными типами во время выполнения мы можем решить, сколько памяти нужно запросить. И то же самое имеет место и здесь. Только массивы с постоянным размером (размер, который может быть определен во время компиляции) могут быть распределены с автоматической продолжительностью хранения (в стеке). Массивы с динамическим размером должны быть выделены в кучу, вызвав new .

(И это то, где останавливается любое сходство с C #)

Теперь все, что выделено в стеке, имеет «автоматическую» продолжительность хранения (вы можете фактически объявить переменную как auto , но это значение по умолчанию, если не указан другой тип хранилища, поэтому ключевое слово на самом деле не используется на практике, но это где оно произошло от)

Автоматическая длительность хранения означает, что это похоже, длительность переменной обрабатывается автоматически. Напротив, все, что выделяется в куче, должно быть вручную удалено вами. Вот пример:

 void foo() { bar b; bar* b2 = new bar(); } 

Эта функция создает три значения, которые стоит учитывать:

В строке 1 он объявляет переменную b типа bar в стеке (автоматическая продолжительность).

В строке 2 он объявляет указатель bar b2 в стеке (автоматическая продолжительность) и вызывает новое, выделяя объект- bar в куче. (динамическая длительность)

Когда функция вернется, произойдет следующее: во-первых, b2 выходит за frameworks (порядок разрушения всегда противоположный порядку построения). Но b2 – это просто указатель, поэтому ничего не происходит, память, которую он занимает, просто освобождается. И что важно, память, на которую он указывает (экземпляр bar в куче), не касается. Только указатель освобождается, потому что только указатель имел автоматическую продолжительность. Во-вторых, b выходит за frameworks, поэтому, поскольку у него есть автоматическая продолжительность, его деструктор вызывается, и память освобождается.

И экземпляр bar в куче? Вероятно, он все еще там. Никто не потрудился удалить его, поэтому у нас просочилась память.

Из этого примера мы можем видеть, что что-либо с автоматической продолжительностью гарантируется, когда его деструктор называется, когда он выходит из сферы действия. Это полезно. Но все, что выделяется в куче, сохраняется до тех пор, пока оно нам нужно, и может быть динамически разным, как в случае с массивами. Это также полезно. Мы можем использовать это для управления распределением памяти. Что, если class Foo выделил некоторую память в куче в своем конструкторе и удалил эту память в своем деструкторе. Тогда мы могли бы получить лучшее из обоих миров, безопасные выделения памяти, которые гарантированно будут освобождены снова, но без ограничений заставить все быть в стеке.

И это почти точно, как работает код C ++. Посмотрите, например, на std::vector стандартной библиотеки. Обычно это выделяется в стеке, но может быть динамически изменено и изменено. И он делает это, внутренне выделяя память на кучу по мере необходимости. Пользователь classа никогда не видит этого, поэтому нет возможности утечки памяти или забывания очистить то, что вы выделили.

Этот принцип называется RAII (инициализация ресурсов – инициализация), и его можно расширить до любого ресурса, который должен быть приобретен и выпущен. (сетевые сокеты, файлы, подключения к базе данных, блокировки синхронизации). Все они могут быть приобретены в конструкторе и выпущены в деструкторе, поэтому вам гарантировано, что все ресурсы, которые вы приобретете, снова будут освобождены.

Как правило, никогда не используйте новый / удалить непосредственно из своего кода высокого уровня. Всегда заверните его в class, который может управлять памятью для вас, и который гарантирует, что он снова освободится. (Да, могут быть исключения из этого правила. В частности, интеллектуальные указатели требуют, чтобы вы вызывали new напрямую и передавали указатель на его конструктор, который затем берет на себя и гарантирует, что delete вызвано правильно. Но это по-прежнему очень важное правило большого пальца)

Какой метод я должен использовать?

Это почти никогда не определяется вашими настройками печати, а контекстом. Если вам нужно держать объект в нескольких стеках или если он слишком тяжел для стека, вы его размещаете в свободном хранилище. Кроме того, поскольку вы выделяете объект, вы также отвечаете за освобождение памяти. Найдите оператор delete .

Чтобы облегчить бремя использования управления бесплатными магазинами, люди изобрели такие вещи, как auto_ptr и unique_ptr . Я настоятельно рекомендую вам взглянуть на них. Они могут даже помочь вашим печатным вопросам 😉

Если вы пишете на C ++, вы, вероятно, пишете о производительности. Использование нового и бесплатного хранилища происходит намного медленнее, чем использование стека (особенно при использовании streamов), поэтому используйте его только тогда, когда вам это нужно.

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

Также старайтесь избегать использования удаления. Вместо этого оберните свой новый в умный указатель. Пусть удаленный вызов-указатель удалит для вас.

Есть некоторые случаи, когда умный указатель не умный. Никогда не храните std :: auto_ptr <> внутри контейнера STL. Он скоро удалит указатель из-за операций копирования внутри контейнера. Другой случай – когда у вас действительно большой контейнер STL указателей на объекты. boost :: shared_ptr <> будет иметь тонну накладных расходов, поскольку он ударяет счетчики ссылок вверх и вниз. Лучший способ пойти в этом случае – положить контейнер STL в другой объект и дать этому объекту деструктор, который будет вызывать удаление на каждом указателе в контейнере.

Короткий ответ: если вы новичок в C ++, вы никогда не должны использовать new или delete себя. Вместо этого вы должны использовать интеллектуальные указатели, такие как std::unique_ptr (или реже, std::shared_ptr ). Таким образом, вам не придется беспокоиться почти столько об утечке памяти. И даже если вы более продвинуты, лучше всего использовать инкапсуляцию пользовательского способа использования new и delete в небольшой class (например, настраиваемый интеллектуальный указатель), который предназначен только для проблем жизненного цикла объекта.

Конечно, за кулисами эти интеллектуальные указатели по-прежнему выполняют динамическое распределение и освобождение, поэтому использование кода с ними будет иметь связанные с этим накладные расходы. Другие ответы здесь касались этих проблем и как принимать проектные решения о том, когда использовать интеллектуальные указатели, а не просто создавать объекты в стеке или включать их в качестве прямых элементов объекта, достаточно хорошо, чтобы я не повторил их. Но мое резюме было бы: не используйте интеллектуальные указатели или динамическое распределение, пока что-то не заставит вас это сделать.

Без new ключевого слова вы храните это в стеке вызовов . Хранение чрезмерно больших переменных в стеке приведет к переполнению стека .

Если ваша переменная используется только в контексте одной функции, вам лучше использовать переменную стека, то есть вариант 2. Как говорили другие, вам не нужно управлять временем жизни переменных стека – они построены и автоматически разрушается. Кроме того, распределение / освобождение переменной в куче медленно сравнивается. Если ваша функция вызывается достаточно часто, вы увидите огромное улучшение производительности, если используете переменные стека и переменные кучи.

Тем не менее, существует несколько очевидных случаев, когда переменные стека недостаточны.

Если переменная стека имеет большой объем памяти, тогда возникает риск переполнения стека. По умолчанию размер стека для каждого streamа составляет 1 МБ в Windows. Маловероятно, что вы создадите переменную стека размером 1 МБ, но вы должны иметь в виду, что использование стека является кумулятивным. Если ваша функция вызывает функцию, которая вызывает другую функцию, которая вызывает другую функцию, которая …, переменные стека во всех этих функциях занимают место в одном стеке. Рекурсивные функции могут быстро справляться с этой задачей, в зависимости от глубины рекурсии. Если это проблема, вы можете увеличить размер стека (не рекомендуется) или присвоить переменную в куче, используя новый оператор (рекомендуется).

Другое, более вероятное условие состоит в том, что ваша переменная должна «жить» вне сферы вашей функции. В этом случае вы бы выделили переменную в куче, чтобы ее можно было выйти за пределы любой заданной функции.

Вы передаете myClass из функции или ожидаете, что она будет существовать вне этой функции? Как говорили некоторые другие, это касается области, когда вы не выделяете кучу. Когда вы покидаете функцию, она уходит (в конце концов). Одной из classических ошибок, допущенных новичками, является попытка создать локальный объект некоторого classа в функции и вернуть его, не выделяя его в кучу. Я помню, что отлаживал подобные вещи еще в мои предыдущие дни, выполняя c ++.

Простым ответом является yes – new () создает объект в куче (с неудачным побочным эффектом, которым вы должны управлять своим временем жизни (путем явного вызова delete на нем), тогда как вторая форма создает объект в стеке в текущей и этот объект будет уничтожен, когда он выходит за frameworks.

Короткий ответ – да, «новое» ключевое слово невероятно важно, так как при его использовании данные объекта хранятся в куче в отличие от стека, что является самым важным!

Второй метод создает экземпляр в стеке, а также такие вещи, как что-то объявленное int и список параметров, которые передаются в функцию.

Первый метод дает место указателю на стек, который вы установили в место в памяти, где новый MyClass был выделен в куче или бесплатном хранилище.

Первый метод также требует, чтобы вы delete то, что вы создаете, с new , тогда как во втором методе class автоматически разрушается и освобождается, когда он выходит из области видимости (обычно следующая закрывающая скобка).

  • Почему ссылки не переустанавливаются в C ++
  • C ++: безопасно ли указывать указатель на int и позже на указатель?
  • С массивами, почему это так, == 5 ?
  • Как изменить указатель, который был передан в функцию в C?
  • Разделение этого указателя дает мне -46, но я не уверен, почему
  • Указатель функций, отлитый от другой подписи
  • Почему для нулевого указателя используется нулевой адрес?
  • Использование fseek с указателем файла, указывающим на stdin
  • Свободно (ptr), где ptr является NULL поврежденной памятью?
  • Что возвращает sizeof (& array)?
  • Является ли законным сравнивать висячие указатели?
  • Interesting Posts

    Используя операционную систему приманки, с другой ОС, скрытой TrueCrypt?

    Рекомендации для компонента .NET для доступа к почтовому ящику электронной почты

    Как обращаться с кнопкой «Назад» в диалоговом окне?

    автоматически resize текста (размер шрифта) при изменении размера windows?

    Как загрузить файл PKCS # 12 в OpenSSL программно?

    Как создать резервную копию и восстановить системный буфер обмена в C #?

    как настроить listview row android

    JSTL / JSP EL (язык выражений) в контексте не JSP (автономный)

    XSLT 3-уровневая группировка по атрибутам

    Добавьте какой-то номер строки в команду / конвейер агрегата mongodb

    Порядок выполнения событий при нажатии PrimeFaces p: commandButton

    Как определить, относится ли страница документации к Google App Engine к стандартной или гибкой среде

    Объединяя SSD и жесткий диск в программном RAID1?

    Плюсы и минусы пакетов частных classов в Java?

    Почему мои компьютеры и Mac не могут передавать друг другу?

    Давайте будем гением компьютера.