Я программист на C, пытаюсь понять C++. Многие учебники демонстрируют создание экземпляров объекта с помощью таких фрагментов, как:
Dog* sparky = new Dog();
что означает, что позже вы сделаете:
delete sparky;
что имеет смысл. Теперь, в случае, когда выделение динамической памяти не требуется, есть ли причина использовать вышеуказанное вместо
Dog sparky;
и позвольте деструктору вызываться, когда Sparky выходит за пределы области видимости?
Спасибо!





Что ж, причина использования указателя будет точно такой же, как причина использования указателей в C, выделенных с помощью malloc: если вы хотите, чтобы ваш объект жил дольше, чем ваша переменная!
Даже настоятельно рекомендуется НЕ использовать новый оператор, если вы можете этого избежать. Особенно, если вы используете исключения. В общем, гораздо безопаснее позволить компилятору освободить ваши объекты.
Единственная причина, по которой я бы беспокоился, это то, что Dog теперь размещается в стеке, а не в куче. Так что, если Dog имеет размер мегабайта, у вас может быть проблема,
Если вам все же нужно перейти по новому / удаленному маршруту, будьте осторожны с исключениями. И из-за этого вы должны использовать auto_ptr или один из типов интеллектуальных указателей boost для управления временем жизни объекта.
Нет причин для нового (в куче), когда вы можете выделить в стеке (если по какой-то причине у вас есть небольшой стек и вы хотите использовать кучу.
Вы можете рассмотреть возможность использования shared_ptr (или одного из его вариантов) из стандартной библиотеки, если вы действительно хотите разместить в куче. Это выполнит удаление за вас, как только все ссылки на shared_ptr перестанут существовать.
Я полагаю, вы могли бы хотеть, чтобы объект пережил область действия функции, но OP, похоже, не предполагал, что они пытались сделать.
Напротив, вы всегда должны отдавать предпочтение распределению стека, поскольку, как показывает практическое правило, вы никогда не должны иметь new / delete в вашем пользовательском коде.
Как вы говорите, когда переменная объявляется в стеке, ее деструктор автоматически вызывается, когда она выходит за пределы области видимости, что является вашим основным инструментом для отслеживания времени жизни ресурса и предотвращения утечек.
Итак, в общем, каждый раз, когда вам нужно выделить ресурс, будь то память (путем вызова new), дескрипторы файлов, сокеты или что-то еще, оберните его в класс, в котором конструктор получает ресурс, а деструктор освобождает его. Затем вы можете создать объект этого типа в стеке, и вам будет гарантировано, что ваш ресурс будет освобожден, когда он выйдет за пределы области видимости. Таким образом, вам не нужно везде отслеживать ваши новые / удаляемые пары, чтобы избежать утечек памяти.
Наиболее распространенное название этой идиомы - RAII.
Также изучите классы интеллектуальных указателей, которые используются для обертывания результирующих указателей в редких случаях, когда вам действительно нужно выделить что-то с новым за пределами выделенного объекта RAII. Вместо этого вы передаете указатель на интеллектуальный указатель, который затем отслеживает его время жизни, например, путем подсчета ссылок, и вызывает деструктор, когда последняя ссылка выходит за пределы области видимости. В стандартной библиотеке есть std::unique_ptr для простого управления на основе области действия и std::shared_ptr, который выполняет подсчет ссылок для реализации совместного владения.
Many tutorials demonstrate object instantiation using a snippet such as ...
Итак, вы обнаружили, что большинство руководств - отстой. ;) Большинство руководств преподают вам паршивые методы работы с C++, включая вызов new / delete для создания переменных, когда в этом нет необходимости, и затрудняющие отслеживание времени жизни ваших выделений.
Необработанные указатели полезны, когда вам нужна семантика, подобная auto_ptr (передача владения), но при этом сохраняется операция замены, не вызывающая выброса, и не нужны накладные расходы на подсчет ссылок. Крайний случай, возможно, но полезный.
Это правильный ответ, но причина, по которой я никогда не привык создавать объекты в стеке, заключается в том, что никогда не бывает совершенно очевидно, насколько большим будет этот объект. Вы просто запрашиваете исключение переполнения стека.
Грег: Конечно. Но, как вы говорите, крайний случай. В общем, указателей лучше избегать. Но они на языке не зря, этого нельзя отрицать. :) dviljoen: Если объект большой, вы помещаете его в объект RAII, который может быть размещен в стеке и содержит указатель на данные, размещенные в куче.
@dviljoen: Нет. Компиляторы C++ не раздувают объекты без надобности. Худшее, что вы увидите, это то, что оно обычно округляется до ближайшего числа, кратного четырем байтам. Обычно класс, содержащий указатель, занимает столько же места, сколько и сам указатель, поэтому использование стека ничего не стоит.
Относитесь к куче как к очень важному объекту недвижимости и используйте его очень разумно. Основное правило большого пальца - использовать стек при любой возможности и использовать кучу всякий раз, когда нет другого пути. Располагая объекты в стеке, вы можете получить множество преимуществ, таких как:
(1). Вам не нужно беспокоиться о раскручивании стека в случае возникновения исключений.
(2). Вам не нужно беспокоиться о фрагментации памяти, вызванной выделением вашим диспетчером кучи больше места, чем необходимо.
Следует учитывать размер объекта. Размер стека ограничен, поэтому очень большие объекты должны выделяться в куче.
@ Адам Я думаю, что Навин здесь прав. Если вы можете положить в стек большой объект, то сделайте это, потому что так лучше. Есть много причин, по которым вы, вероятно, не можете этого сделать. Но я думаю, что он прав.
Я видел этот антипаттерн от людей, которые не совсем понимают оператор & address-of. Если им нужно вызвать функцию с указателем, они всегда будут выделять память в куче, чтобы получить указатель.
void FeedTheDog(Dog* hungryDog);
Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;
Dog goodDog;
FeedTheDog(&goodDog);
В качестве альтернативы: void FeedTheDog (Dog & hungryDog); Собака Собака; FeedTheDog (собака);
@ScottLangham, правда, но я не об этом пытался сказать. Иногда вы вызываете функцию, которая принимает, например, необязательный NULL-указатель, или, может быть, это существующий API, который нельзя изменить.
У меня была такая же проблема в Visual Studio. Вы должны использовать:
yourClass-> classMethod ();
скорее, чем:
yourClass.classMethod ();
Это не отвечает на вопрос. Вы скорее замечаете, что для доступа к объекту, размещенному в куче (через указатель на объект), и объекту, размещенному в стеке, необходимо использовать другой синтаксис.
Есть еще одна причина, о которой никто не упомянул, почему вы можете создать свой объект динамически. Динамические объекты на основе кучи позволяют использовать полиморфизм.
Вы также можете полиморфно работать с объектами на основе стека, я не вижу никакой разницы между объектами, выделенными стеком / и кучей, в этом отношении. Например: void MyFunc () {Dog dog; Кормить собаку); } void Feed (Животные и животные) {auto food = animal-> GetFavouriteFood (); PutFoodInBowl (еда); } // В этом примере GetFavouriteFood может быть виртуальной функцией, которую каждое животное переопределяет своей собственной реализацией. Это будет работать полиморфно без использования кучи.
-1: полиморфизм требует только ссылки или указателя; он полностью не зависит от продолжительности хранения базового экземпляра.
@underscore_d "Использование универсального базового класса требует затрат: объекты должны быть выделены в куче, чтобы быть полиморфными;" - Бьярн Страуструп stroustrup.com/bs_faq2.html#object
LOL, меня не волнует, сказал ли это сам Страуструп, но для него это невероятно смущающая цитата, потому что он ошибается. Вы знаете, нетрудно проверить это самостоятельно: создайте экземпляры DerivedA, DerivedB и DerivedC в стеке; создайте также выделенный стеком указатель на Base и подтвердите, что вы можете разместить его с помощью A / B / C и использовать их полиморфно. Применяйте критическое мышление и стандартные ссылки к утверждениям, даже если они исходят от автора языка. Здесь: stackoverflow.com/q/5894775/2757035
Скажем так, у меня есть объект, содержащий 2 отдельных семейства почти по 1000 полиморфных объектов в каждом с автоматической продолжительностью хранения. Я создаю экземпляр этого объекта в стеке, и, обращаясь к этим членам через ссылки или указатели, он достигает всех возможностей полиморфизма над ними. Ничто в моей программе не выделяется динамически / в куче (кроме ресурсов, принадлежащих классам, выделенным стеком), и это не меняет возможности моего объекта ни на йоту.
Хотя наличие вещей в стеке может быть преимуществом с точки зрения распределения и автоматического освобождения, у него есть некоторые недостатки.
Возможно, вы не захотите размещать огромные объекты в стеке.
Динамическая рассылка! Рассмотрим этот код:
#include <iostream>
class A {
public:
virtual void f();
virtual ~A() {}
};
class B : public A {
public:
virtual void f();
};
void A::f() {cout << "A";}
void B::f() {cout << "B";}
int main(void) {
A *a = new B();
a->f();
delete a;
return 0;
}
Это напечатает "B". Теперь посмотрим, что происходит при использовании Stack:
int main(void) {
A a = B();
a.f();
return 0;
}
Будет выведено «A», что может быть не интуитивно понятно тем, кто знаком с Java или другими объектно-ориентированными языками. Причина в том, что у вас больше нет указателя на экземпляр B. Вместо этого создается экземпляр B и копируется в переменную a типа A.
Некоторые вещи могут происходить неожиданно, особенно если вы новичок в C++. В C у вас есть указатели, и все. Вы знаете, как их использовать, и они ВСЕГДА делают то же самое. В C++ это не так. Только представьте, что происходит, когда вы используете в этом примере в качестве аргумента для метода - все становится сложнее, и это имеет огромное значение, если a относится к типу A, или A*, или даже A& (вызов по ссылке). Возможны многие комбинации, и все они ведут себя по-разному.
-1: люди, не способные понять семантику значений, и тот простой факт, что полиморфизму нужна ссылка / указатель (чтобы избежать нарезки объектов), не представляют собой каких-либо проблем с языком. Возможности C++ не следует рассматривать как недостаток только потому, что некоторые люди не могут изучить его основные правила.
Спасибо за внимание. У меня была аналогичная проблема с методом, принимающим объект вместо указателя или ссылки, и что это был за беспорядок. У объекта есть указатели внутри, и они были удалены слишком рано из-за этого копирования.
@underscore_d Согласен; последний абзац этого ответа следует удалить. Не воспринимайте тот факт, что я отредактировал этот ответ, как то, что я согласен с ним; Я просто не хотел, чтобы в нем читались ошибки.
@TamaMcGlinn согласился. Спасибо за хорошее редактирование. Я удалил часть мнения.
Есть много причин, например, см. Мой ответ.