Между ними есть важное различие.
Все, что не выделяется с помощью, во new
многом похоже на типы значений в C # (и люди часто говорят, что эти объекты размещаются в стеке, что, вероятно, является наиболее распространенным / очевидным случаем, но не всегда верно. Точнее, объекты, выделенные без использования, new
имеют автоматическое хранение Продолжительность
Все, что выделено с new
, размещается в куче, и возвращается указатель на него, точно так же, как ссылочные типы в C #.
Все, что размещено в стеке, должно иметь постоянный размер, определенный во время компиляции (компилятор должен правильно установить указатель стека или, если объект является членом другого класса, он должен отрегулировать размер этого другого класса) , Вот почему массивы в C # являются ссылочными типами. Они должны быть, потому что со ссылочными типами мы можем решить во время выполнения, какой объем памяти запрашивать. И то же самое здесь. Только массивы с постоянным размером (размер, который может быть определен во время компиляции) могут быть распределены с автоматической продолжительностью хранения (в стеке). Динамически измеренные массивы должны быть выделены в куче, путем вызова new
.
(И на этом любое сходство с C # прекращается)
Теперь все, что размещено в стеке, имеет «автоматическую» продолжительность хранения (вы можете объявить переменную как auto
, но это значение по умолчанию, если другой тип хранилища не указан, поэтому ключевое слово на практике не используется, но именно здесь оно происходит от)
Длительность автоматического хранения означает, как именно это звучит, длительность переменной обрабатывается автоматически. Напротив, все, что размещено в куче, должно быть удалено вами вручную. Вот пример:
void foo() {
bar b;
bar* b2 = new bar();
}
Эта функция создает три значения, которые стоит рассмотреть:
В строке 1 объявляется переменная b
типа bar
в стеке (автоматическая продолжительность).
В строке 2 он объявляет bar
указатель b2
на стек (автоматическая длительность) и вызывает new, выделяя bar
объект в куче. (динамическая продолжительность)
Когда функция вернется, произойдет следующее: во-первых, b2
выходит из области видимости (порядок уничтожения всегда противоположен порядку построения). Но b2
это всего лишь указатель, поэтому ничего не происходит, память, которую он занимает, просто освобождается. И что важно, память, на которую он указывает ( bar
экземпляр в куче), НЕ затрагивается. Только указатель освобождается, потому что только указатель имел автоматическую продолжительность. Во-вторых, b
выходит из области видимости, поэтому, поскольку он имеет автоматическую продолжительность, вызывается его деструктор и освобождается память.
А bar
экземпляр в куче? Это, вероятно, все еще там. Никто не удосужился удалить его, поэтому у нас просочилась память.
Из этого примера мы можем видеть, что любой объект с автоматической продолжительностью гарантированно будет вызывать свой деструктор, когда он выходит из области видимости. Это полезно Но все, что размещено в куче, длится столько времени, сколько нам нужно, и может иметь динамический размер, как в случае массивов. Это тоже полезно. Мы можем использовать это для управления распределением памяти. Что если класс Foo выделил некоторую память в куче в своем конструкторе и удалил эту память в своем деструкторе. Тогда мы могли бы получить лучшее из обоих миров - безопасное распределение памяти, которое гарантированно будет освобождено снова, но без ограничений, заставляющих все быть в стеке.
И это именно то, как работает большинство кода C ++. Посмотрите на стандартную библиотеку, std::vector
например. Это обычно выделяется в стеке, но может иметь динамический размер и размер. И делает это путем внутреннего выделения памяти в куче по мере необходимости. Пользователь этого класса никогда не увидит этого, поэтому нет никаких шансов утечки памяти или забывания очистить то, что вы выделили.
Этот принцип называется RAII (Resource Acquisition is Initialization), и его можно распространить на любой ресурс, который необходимо получить и освободить. (сетевые сокеты, файлы, соединения с базой данных, блокировки синхронизации). Все они могут быть получены в конструкторе и выпущены в деструкторе, так что вы гарантированно, что все ресурсы, которые вы приобретаете, будут освобождены снова.
Как правило, никогда не используйте new / delete непосредственно из кода высокого уровня. Всегда оборачивайте его в класс, который может управлять памятью для вас и который обеспечит ее освобождение снова. (Да, могут быть исключения из этого правила. В частности, умные указатели требуют, чтобы вы вызывали new
напрямую и передавали указатель на его конструктор, который затем вступает во владение и обеспечивает delete
правильный вызов. Но это все еще очень важное практическое правило )