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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

Ответ 5

Краткий ответ: если вы новичок в C++, вам никогда не следует использовать new или delete себя.

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

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

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

Ответ 10

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

Ответ 11

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

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

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