Объектная реализация C ++ - PullRequest
       13

Объектная реализация C ++

111 голосов
/ 02 декабря 2008

Я программист на C, пытаюсь понять C ++. Многие учебные пособия демонстрируют создание объектов с использованием фрагмента, например:

Dog* sparky = new Dog();

, что подразумевает, что позже вы сделаете:

delete sparky;

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

Dog sparky;

и позвольте деструктору позвонить, как только искра выйдет из области видимости?

Спасибо!

Ответы [ 9 ]

161 голосов
/ 02 декабря 2008

Наоборот, вы всегда должны отдавать предпочтение выделению стека, если вы не хотите, чтобы в вашем пользовательском коде было новое / удаление.

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

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

Наиболее распространенное название для этой идиомы: RAII

Также обратите внимание на классы интеллектуальных указателей, которые используются для оборачивания результирующих указателей в тех редких случаях, когда вам нужно выделить что-то новое за пределами выделенного объекта RAII. Вместо этого вы передаете указатель интеллектуальному указателю, который затем отслеживает его время жизни, например, путем подсчета ссылок, и вызывает деструктор, когда последняя ссылка выходит из области видимости. Стандартная библиотека имеет std::unique_ptr для простого управления на основе области и std::shared_ptr, который выполняет подсчет ссылок для реализации совместного владения.

Многие учебники демонстрируют объект создание экземпляра с использованием фрагмента, например ...

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

21 голосов
/ 29 июня 2013

Хотя наличие объектов в стеке может быть преимуществом с точки зрения выделения и автоматического освобождения, у него есть некоторые недостатки.

  1. Возможно, вы не захотите размещать огромные объекты в стеке.

  2. Динамическая отправка! Рассмотрим этот код:

#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". Теперь посмотрим, что происходит при использовании стека:

int main(void) {
  A a = B();
  a.f();
  return 0;
}

При этом будет напечатано «A», что может быть не интуитивно понятно тем, кто знаком с Java или другими объектно-ориентированными языками. Причина в том, что у вас больше нет указателя на экземпляр B. Вместо этого создается экземпляр B, который копируется в переменную a типа A.

.

Некоторые вещи могут происходить неинтуитивно, особенно когда вы новичок в C ++. В Си у тебя есть свои указатели и все тут. Вы знаете, как их использовать, и они всегда делают то же самое. В C ++ это не так. Только представьте, что происходит, когда вы используете в этом примере в качестве аргумента для метода - все усложняется, и ОЧЕНЬ важно, если a имеет тип A или A* или даже A& ( вызов по ссылке). Возможны многие комбинации, и все они ведут себя по-разному.

13 голосов
/ 02 декабря 2008

Я видел этот шаблон от людей, которые не совсем понимают оператор & address-of. Если им нужно вызвать функцию с указателем, они всегда будут размещаться в куче, чтобы получить указатель.

void FeedTheDog(Dog* hungryDog);

Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;

Dog goodDog;
FeedTheDog(&goodDog);
13 голосов
/ 02 декабря 2008

Ну, причина использования указателя будет точно такой же, как причина использовать указатели в C, выделенные с помощью malloc: если вы хотите, чтобы ваш объект жил дольше, чем ваша переменная!

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

7 голосов
/ 02 декабря 2008

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

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

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

5 голосов
/ 02 декабря 2008

Единственная причина, по которой я беспокоюсь, это то, что Dog теперь размещается в стеке, а не в куче. Так что, если собака размером с мегабайт, у вас может быть проблема,

Если вам нужно перейти на новый / удалить маршрут, будьте осторожны с исключениями. И из-за этого вы должны использовать auto_ptr или один из интеллектуальных указателей типа boost для управления временем жизни объекта.

1 голос
/ 02 декабря 2008

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

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

0 голосов
/ 13 марта 2013

Существует еще одна причина, о которой никто не упомянул, почему вы можете создать свой объект динамически. Динамические объекты на основе кучи позволяют использовать полиморфизм .

0 голосов
/ 13 августа 2012

У меня была такая же проблема в Visual Studio. Вы должны использовать:

yourClass-> classMethod ();

вместо:

yourClass.classMethod ();

...