Почему программисты C ++ минимизируют использование «новых»? [c++]
Поскольку стек является быстрым и надежным
В C ++ требуется только одна команда для распределения пространства - в стеке - для каждого объекта локальной области в заданной функции, и невозможно утечка какой-либо из этой памяти. Этот комментарий предполагал (или должен был предполагать) сказать что-то вроде «использовать стек, а не кучу».
Я наткнулся на вопрос «Переполнение стека». Утечка памяти с помощью std :: string при использовании std :: list <std :: string> , и один из комментариев говорит об этом:
Прекратите использовать так много. Я не вижу причин, по которым вы использовали новое в любом месте. Вы можете создавать объекты по значению в C ++, и это одно из огромных преимуществ использования языка. Вам не нужно выделять все в стек *. Перестаньте думать, как программист на Java.
Я не совсем уверен, что он имеет в виду. Почему объекты должны создаваться по значению на C ++ как можно чаще, и какая разница делает его внутренне? Я неправильно понял ответ?
* Комментарий первоначально сказал «стек», с тех пор он был исправлен модератором до предполагаемой «кучи».
Объекты, созданные new
должны быть в конечном итоге delete
чтобы они не протекали. Деструктор не будет вызван, память не будет освобождена, весь бит. Поскольку C ++ не содержит сборку мусора, это проблема.
Объекты, созданные по значению (т.е. на стеке), автоматически умирают, когда они выходят за рамки. Деструкторный вызов вставляется компилятором, и память автоматически освобождается при возврате функции.
Умные указатели, такие как auto_ptr
, shared_ptr
решают проблему оборванных ссылок, но они требуют дисциплины кодирования и имеют другие проблемы (копируемость, циклы ссылок и т. Д.).
Кроме того, в сценариях с многопоточным сценарием new
- это точка раздора между потоками; Может быть влияние производительности на чрезмерное использование new
. Создание объекта стека по определению thread-local, поскольку каждый поток имеет свой собственный стек.
Недостатком объектов ценности является то, что они умирают после возвращения функции хоста - вы не можете передавать ссылку на тех, кто возвращается к вызывающему, только путем копирования или возврата по значению.
В значительной степени это тот, кто поднимает свои собственные слабости до общего правила. По сути , нет ничего плохого в создании объектов с использованием new
оператора. Для чего есть аргумент, так это то, что вы должны сделать это с некоторой дисциплиной: если вы создаете объект, вам нужно убедиться, что он будет уничтожен.
Самый простой способ сделать это - создать объект в автоматическом хранилище, поэтому C ++ знает, как его уничтожить, когда он выходит за рамки:
{
File foo = File("foo.dat");
// do things
}
Теперь заметим, что когда вы отвалитесь от этого блока после конечной скобки, foo
выходит за рамки. C ++ вызовет его dtor автоматически для вас. В отличие от Java, вам не нужно ждать, пока GC ее обнаружит.
Если бы вы написали
{
File * foo = new File("foo.dat");
Вы хотели бы явно сопоставить это с
delete foo;
}
Или даже лучше, выделите свой File *
как «умный указатель». Если вы не будете осторожны, это может привести к утечкам.
Сам ответ ошибочно полагает, что если вы не используете new
вы не выделяете кучу; На самом деле, на C ++ вы этого не знаете. В лучшем случае вы знаете, что в стеке выделяется небольшой объем памяти, скажем, одного указателя. Однако рассмотрим, является ли реализация файла чем-то вроде
class File {
private:
FileImpl * fd;
public:
File(String fn){ fd = new FileImpl(fn);}
FileImpl
все равно будет выделен в стеке.
И да, вы должны быть уверены, что
~File(){ delete fd ; }
В классе; Без него вы будете утечка памяти из кучи, даже если вы явно не выделили кучу.
Когда вы используете новое, объекты выделяются в кучу. Он обычно используется, когда вы ожидаете расширения. Когда вы объявляете объект, например,
Class var;
Он помещается в стек.
Вам всегда придется вызывать уничтожение объекта, который вы разместили в куче, с новым. Это открывает потенциал для утечек памяти. Объекты, помещенные в стек, не подвержены утечке памяти!
Потому что он подвержен тонким утечкам, даже если вы оберните результат умным указателем .
Рассмотрим «осторожного» пользователя, который помнит об обертке объектов в интеллектуальных указателях:
foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));
Этот код опасен, потому что нет гарантии, что либо shared_ptr
будет сконструирован до T1
либо T2
. Следовательно, если один из new T1()
или new T2()
терпит неудачу после того, как другой преуспеет, тогда первый объект будет просочиться, потому что shared_ptr
существует, чтобы уничтожить и освободить его.
SolutIon: используйте make_shared
.
Я склонен не соглашаться с идеей использования нового «слишком много». Хотя использование оригинального плаката нового с системными классами немного смешно. ( int *i; i = new int[9999];
Действительно? int i[9999];
гораздо яснее.) Я думаю, что это то, что получало козел комментатора.
Когда вы работаете с системными объектами, очень редко вам понадобится более одной ссылки на тот же самый объект. Пока значение одно и то же, это все, что имеет значение. И системные объекты обычно не занимают много места в памяти. (Один байт на символ, в строке). И если они это сделают, библиотеки должны быть разработаны с учетом управления памятью (если они хорошо написаны). В этих случаях (все, кроме одного или двух из новостей в его коде) новое практически бессмысленно и служит лишь для того, чтобы вводить путаницы и возможности для ошибок.
Однако, когда вы работаете со своими собственными классами / объектами (например, с классом Line оригинального плаката), вы должны начать думать о проблемах, таких как объем памяти, постоянство данных и т. Д. Самостоятельно. На данный момент использование нескольких ссылок на одно и то же значение неоценимо - оно позволяет создавать такие конструкции, как связанные списки, словари и графики, где несколько переменных должны иметь не только одно и то же значение, но и ссылаться на один и тот же объект в памяти. Однако класс Line не имеет ни одного из этих требований. Таким образом, исходный код плаката фактически не нуждается в new
.
Две причины:
- В этом случае нет необходимости. Вы делаете свой код излишне сложным.
- Он выделяет пространство в куче, и это означает, что вы должны помнить, что позже
delete
его, или это вызовет утечку памяти.
new
- новый.
Напомним, почему goto
настолько оскорблен: хотя он является мощным инструментом низкого уровня для управления потоком, люди часто использовали его излишне сложными способами, из-за которых сложный код не выполнялся. Более того, наиболее полезные и простые для чтения шаблоны были закодированы в структурированных операциях программирования (например, for
или в while
); Конечный эффект заключается в том, что код, где goto
является подходящим способом, довольно редок, если вы соблазняетесь писать goto
, вы, вероятно, плохо что-то делаете (если вы действительно не знаете, что делаете).
new
схож - он часто используется для того, чтобы сделать вещи излишне сложными, а тяжелее читать, а наиболее полезные шаблоны использования могут быть закодированы, закодированы в разные классы. Кроме того, если вам нужно использовать любые новые шаблоны использования, для которых еще нет стандартных классов, вы можете написать свои собственные классы, которые их кодируют!
Я бы даже утверждал, что new
хуже, чем goto
, из-за необходимости соединять new
и delete
утверждения.
Как и goto
, если вы когда-нибудь думаете, что вам нужно использовать new
, вы, вероятно, делаете что-то плохое, особенно если вы делаете это за пределами реализации класса, целью которого является инкапсуляция любых динамических распределений, которые вам нужно сделать.