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

StackOverflow https://stackoverflow.com/questions/655065

Вопрос

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

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

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

2) Без новый ключевое слово...

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

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

Трудность, по-видимому, заключается в том, что метод 1 сложнее использовать со стандартными классами 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д;и это всегда следует удалять, независимо от того, какой путь управления выбран или создаются исключения.

Способ 2 (без использования new)

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

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

Несколько простых случаев:

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

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

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

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

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

(И на этом сходство с C # прекращается)

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

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

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

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

В строке 1 объявляется переменная std::vector типа delete в стеке (автоматическая продолжительность).

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

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

А <=> экземпляр в куче? Это, вероятно, все еще там. Никто не удосужился удалить его, поэтому у нас произошла утечка памяти.

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

И именно так работает большинство кодов C ++. Посмотрите на стандартную библиотеку <=>, например. Это обычно выделяется в стеке, но может иметь динамический размер и размер. И делает это путем внутреннего распределения памяти в куче по мере необходимости. Пользователь этого класса никогда не увидит этого, поэтому нет шансов утечки памяти или забывания очистить то, что вы выделили.

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

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

  

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

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

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

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

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

Кроме того, старайтесь избегать использования удаления. Вместо этого оберните ваш новый в умный указатель. Позвольте интеллектуальному указателю вызвать удаление для вас.

В некоторых случаях умный указатель не является умным. Никогда не храните std :: auto_ptr & Lt; & Gt; внутри контейнера STL. Он удалит указатель слишком рано из-за операций копирования внутри контейнера. Другой случай, когда у вас есть действительно большой контейнер указателей STL на объекты. повышение :: shared_ptr л & & Гт будет иметь тонну накладных расходов скорости, поскольку это увеличивает счетчик ссылок вверх и вниз. В этом случае лучше всего поместить контейнер STL в другой объект и дать этому объекту деструктор, который будет вызывать delete для каждого указателя в контейнере.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top