C++: разница между использованием нового ключевого слова и отсутствием при создании экземпляров членов класса?

Для задания по программированию нам дан шаблонный класс с двумя членами, объявленными не как указатели, а как реальные объекты:

Foo member;

В конструкторе я сначала попробовал member = *(new Foo());, но узнал, что, по крайней мере иногда, он копировал новый объект Foo и, следовательно, вызывал утечки памяти.

Наконец я обнаружил member = Foo(), а затем посмотрел, в чем разница. Я узнал, что элемент будет выделен в стеке, а не в куче, и что он будет удален, как только выйдет за пределы области видимости. Но как это работает для объектов?

Удаляется ли член только при удалении родительского объекта/класса?

У меня также есть еще один вопрос о member = *(new Foo());. Я инициализировал две переменные-члены одного типа:

// Members
Foo member1;
Foo member2;

// Constructor {
    member1 = *(new Foo());
    member2 = *(new Foo());
}

Почему-то казалось, что member1 не копируется и сохранил тот же адрес, что и исходный Foo (т.е. утечки памяти при удалении не было). member2, однако, будет скопирован и будет иметь другой адрес, и произойдет утечка памяти. Есть ли этому объяснение?


person Joey    schedule 13.02.2011    source источник
comment
Если у вас его еще нет, вам следует получить хороший вводный Книга по С++. Объектная модель C++ полностью отличается от большинства других широко используемых языков программирования, и важно понимать ее основы, если вы собираетесь писать код на C++.   -  person James McNellis    schedule 13.02.2011
comment
Не знаю насчет совсем другого, но что-то фундаментальное здесь точно пропущено.   -  person Fred Nurk    schedule 13.02.2011
comment
@Джеймс Макнеллис: я хотел бы услышать, как вы классифицируете это как принципиально другое. Единственная объектная модель, с которой действительно можно сравнить, — это Java, поскольку другие широко используемые языки, скорее всего, являются языками сценариев. Тем не менее, фундаментальные принципы указателей и распределения, скорее всего, одинаковы для всех языков, просто реализованы по-разному. Однако то, как они освобождаются и разыменовываются, может различаться.   -  person vol7ron    schedule 13.02.2011
comment
@vol7ron: На эту тему можно написать книгу (и я уверен, что у кого-то есть...). Очевидно, что объектная модель C++ фундаментально отличается от объектных моделей Java и .NET: времена жизни детерминированы, а модель ориентирована на значения, а не на ссылки. Объектная модель C++ также фундаментально отличается от модели C: C не имеет понятия построения или уничтожения, а все операции копирования и присваивания фактически представляют собой необработанные копии памяти. По-прежнему вполне возможно рассматривать языки сценариев, большинство из которых, например Java и .NET, имеют недетерминированное время жизни объекта.   -  person James McNellis    schedule 13.02.2011
comment
@James: я давно не программировал на C/C++, так что спасибо, что не восприняли мой вопрос как грубый. Я не уверен, как C вошел в вопрос, поскольку я помню, что ему не хватало объектов class и нужно было сильно полагаться на structs, но мы говорили о C++. Я думал, что основные принципы создания объектов в C++/Java одинаковы, и я не уверен, что понимаю, что вы подразумеваете под детерминированным временем жизни. Тем не менее, я думаю, вы уловили то, что нужно было сказать в комментариях, сказав, что один из них ориентирован на ценность, а другой - на ссылку.   -  person vol7ron    schedule 13.02.2011
comment
@vol7ron: Нет, твой вопрос совсем не показался грубым! (Хотя мой ответ был немного легкомысленным, извините.) Что касается детерминизма: в программе на C++ вы всегда можете точно знать, когда объект создается и когда он уничтожается: есть четко определенные правила, которые определяют, когда вызываются конструкторы и деструкторы. . Таким образом, время жизни объекта детерминировано. В Java и C# вы знаете, когда создаются объекты, но не знаете, когда они уничтожаются. Объект уничтожается в какой-то момент после того, как вы закончите его использование сборщиком мусора. Таким образом, время жизни недетерминировано.   -  person James McNellis    schedule 14.02.2011


Ответы (2)


Ваш анализ неверен. Оба из них являются утечками памяти. Что вы делаете, так это выделяете новую копию объекта, присваиваете значение члену, а затем отбрасываете указатель на выделенную память, не освобождая эту память. В обоих случаях это утечка памяти.

Теперь рассмотрим следующий код:

class MemberType {
  public:
    MemberType() { std::cout << "Default constructor" << std::endl; }
    MemberType(int) { std::cout << "Int constructor" << std::endl; }
};

class Example1 {
  public:
     Example1() {}
  private:
     MemberType member_;
};

class Example2 {
  public:
      Example2() : member_(5) {}
  private:
      MemberType member_;
};

int main(int argc, char** argv) {
   Example1 example1;
   Example2 example2;
   return 0;
}

Этот код будет печатать оба разных типа конструкторов. Обратите внимание, что для инициализации элемента способом по умолчанию вообще не требуется никакого кода инициализации. Следовательно, ваш новый оператор (или даже присваивание без нового) будет ненужным в случае инициализации по умолчанию. При инициализации элементов с помощью конструктора, отличного от конструктора по умолчанию, правильный способ сделать это — использовать список инициализаторов. (Список инициализаторов — это то, что происходит с «: member_(5)» в примере.

Дополнительные сведения о создании объектов в C++ см. в часто задаваемых вопросах по C++ по конструкторам. .

person Michael Aaron Safyan    schedule 13.02.2011
comment
Большое спасибо, это было действительно полезно. - person Joey; 13.02.2011

member = *(new Foo());

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

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

Это неправильный способ инициализации объекта.

member = Foo();

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

Обратите внимание, что предпочтительным способом инициализации переменных-членов является использование списка инициализаторов:

struct C {
    Foo member1, member2;

    C() : member1(), member2() { }
};
person James McNellis    schedule 13.02.2011