Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > C/C++ программирование > Общие вопросы C/C++
Регистрация

Восстановить пароль

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 21.12.2012, 14:22   #1
Suby
Пользователь
 
Аватар для Suby
 
Регистрация: 03.11.2012
Сообщений: 89
По умолчанию Выделение памяти New. Конструкторы копирования и присваивания.

Всем привет!
Вобщем решил вчера потренироваться в недавно-пройденном материале по выделению памяти new для указателей на данные в конструкторах класса и delete в деструкторе. И что-то в совсем застрял. А именно проблема с управлением памятью с помощью кострукторов копирования и присваивания. Накалякал следующее:
work.h
Код:
#ifndef WORK_H_
#define WORK_H_

#include <iostream>
#include <cstring>
#include <string>
using namespace std;

class Work
{
private:
	static int num;    // счетчик вызовов конструкторов и деструкторов
	string * name;     // указатель на строку
	char * surname;    // указатель на строку
public:
	Work();                                 // конструктор по умолчанию
	Work(const string & n, const char * s); // конструктор
	Work(const Work & n);                   // конструктор копирования
	Work & operator=(const Work & s);       // конструктор присваивания
	~Work();                                // деструктор
};

#endif WORK_H_
work.cpp
Код:
#include "work.h"

// счетчик вызовов конструкторов и деструкторов
int Work::num = 0;

Work::Work()                                    // конструктор по умолчанию
{
	num++;                   // + 1 объект
	name = new string("No"); // выделение памяти и инициализация name
	surname = new char[5];   // выделение памяти для surname
	strcpy(surname, "Name"); // инициализация surname
	cout << num << ": " << *name << ' ' << surname << " created by default constructor\n";
}

Work::Work(const string & n, const char * s)    // конструктор
{
	num++;                             // + 1 объект
	name = new string;                 // выделение памяти для name
	*name = n;                         // инициализация name
	surname = new char[strlen(s) + 1]; // выделение памяти для surname
	strcpy(surname, s);                // инициализация surname
	cout << num << ": " << *name << ' ' << surname
		 << " created by initialization constructor\n";
}

Work::Work(const Work & n)                      // конструктор копирования
{
	num++;                                      // + 1 объект
	name = new string;                          // выделение памяти для name
	name = n.name;                              // инициализация name
	surname = new char[strlen(n.surname) + 1];  // выделение памяти для surname
	strcpy(surname, n.surname);                 // инициализация surname
	cout << num << ": " << *name << ' ' << surname << " copied by copy constructor\n";
}

Work & Work::operator=(const Work & s)          // конструктор присваивания
{
	num--;                                      // - 1 объект
	cout << num << ": " << *name << ' ' << surname << " Deleted by = constructor\n";
	delete name;                                // удаляем name, созданное конструктором по умолчанию
	delete [] surname;                          // удаляем surname, созданное конструктором по умолчанию
	num++;                                      // + 1 объект
	name = new string;                          // выделение памяти для name
	name = s.name;                              // инициализация name
	surname = new char[strlen(s.surname) + 1];  // выделение памяти для surname
	strcpy(surname, s.surname);                 // инициализация surname
	cout << num << ": " << *name << ' ' << surname << " copyed by = constructor\n";
	return *this;                               // возвращаем объект
}

Work::~Work()                                   // деструктор
{
	num--;                                      // - 1 объект
	cout << num << ": " << *name << " deleted by destructor\n";
	delete name;                                // удаляем name
	delete [] surname;                          // удаляем surname
}
main.cpp
Код:
#include "work.h"

int main()
{
	{
	Work one("One", "One");  // создание и инициализация объекта конструктором
	Work two("Two", "Two");  // создание и инициализация объекта конструктором
	Work three = one;        // создание и инициализация объекта конструктором копирования
	Work four;               // создание объекта конструктором по умолчанию
	four = two;              // создание и инициализация объекта конструктором присваивания
	one.~Work();             // явный вызов деструктора
	two.~Work();             // явный вызов деструктора
	three.~Work();           // явный вызов деструктора   ВОТ ЗДЕСЬ ПРОГА РУШИТСЯ
	four.~Work();            // явный вызов деструктора   И ЗДЕСЬ ТОЖЕ РУШИТСЯ
	}

	system("pause");
	return 0;
}
Прога крашится при вызове деструктора для объектов, которые были созданы путем копирования или присваивания другого объекта. Киплю два дня над задачей уже, всю голову сломал, не пойму где ошибся. Помогите разобраться пожалуйста.
На оформление кода не смотрите, это состряпано чисто для освежения моей памяти
Suby вне форума Ответить с цитированием
Старый 21.12.2012, 14:36   #2
waleri
Старожил
 
Регистрация: 13.07.2012
Сообщений: 6,372
По умолчанию

Код:
name = s.name
name вроде выделяется через new, потом в копи конструкторе копируем указатель... потом срабатывают два деструктора и пытаются освободить одну и ту же память.

Кстати, зачем выделять память под string, не проще сделать его обычной переменной класса?
waleri вне форума Ответить с цитированием
Старый 21.12.2012, 14:47   #3
EUGY
Форумчанин
 
Аватар для EUGY
 
Регистрация: 11.07.2010
Сообщений: 914
По умолчанию

Принципиальная ошибка - статический счетчик.
Объекты могут содержать разные строки, а счетчик почему-то один.
И кроме того, где эта проверка в деструкторе, мол счетчик не нулевой - строку не удаляем.
EUGY вне форума Ответить с цитированием
Старый 21.12.2012, 14:48   #4
Abstraction
Старожил
 
Аватар для Abstraction
 
Регистрация: 25.10.2011
Сообщений: 3,178
По умолчанию

operator= - не конструктор. Он не изменяет количества существующих объектов.
Код:
name = new string; //У кучи попросили память, получили указатель на начало блока
	name = n.name; //Записали поверх адрес совершенно другого блока.
//1) Утечка памяти
//2) this и n теперь связаны общим указателем на один и тот же блок памяти
Ещё вопросы?
Abstraction вне форума Ответить с цитированием
Старый 21.12.2012, 14:50   #5
Abstraction
Старожил
 
Аватар для Abstraction
 
Регистрация: 25.10.2011
Сообщений: 3,178
По умолчанию

Цитата:
Объекты могут содержать разные строки, а счетчик почему-то один.
???
И, по задумке автора, должен считать общее количество существующих объектов типа Work. Это же одно число, верно?
Abstraction вне форума Ответить с цитированием
Старый 21.12.2012, 14:55   #6
EUGY
Форумчанин
 
Аватар для EUGY
 
Регистрация: 11.07.2010
Сообщений: 914
По умолчанию

На кой черт их считать, если это ни где не используется
Задумка-то наверное была как подсчет ссылок, но что-то не дописано.
EUGY вне форума Ответить с цитированием
Старый 21.12.2012, 14:59   #7
Suby
Пользователь
 
Аватар для Suby
 
Регистрация: 03.11.2012
Сообщений: 89
По умолчанию

Цитата:
Сообщение от waleri Посмотреть сообщение
Код:
name = s.name
name вроде выделяется через new, потом в копи конструкторе копируем указатель... потом срабатывают два деструктора и пытаются освободить одну и ту же память.
Да, я это уже понял, что одни и те же данные удаляются два раза, но чет не пойму как скопировать по-нормальному.
Цитата:
Сообщение от waleri Посмотреть сообщение
[CODE]Кстати, зачем выделять память под string, не проще сделать его обычной переменной класса?
Проще конечно. Но сделал так, чтоб жизнь медом не казалась и понять сам принцип.
Цитата:
Сообщение от EUGY Посмотреть сообщение
Принципиальная ошибка - статический счетчик.
Объекты могут содержать разные строки, а счетчик почему-то один.
И кроме того, где эта проверка в деструкторе, мол счетчик не нулевой - строку не удаляем.
Да на это пофигу, он там для наглядности чисто. Не обращайте на эти мелочи внимание, мне важно сам принцип управлением памяти понять.

Цитата:
Сообщение от EUGY Посмотреть сообщение
На кой черт их считать, если это ни где не используется
Задумка-то наверное была как подсчет ссылок, но что-то не дописано.
Задумки вообще нет никакой. Этот код чисто как самостоятельная проверочная работа, выданная самому себе

Цитата:
Сообщение от Abstraction Посмотреть сообщение
operator= - не конструктор. Он не изменяет количества существующих объектов.
Код:
name = new string; //У кучи попросили память, получили указатель на начало блока
	name = n.name; //Записали поверх адрес совершенно другого блока.
//1) Утечка памяти
//2) this и n теперь связаны общим указателем на один и тот же блок памяти
Ещё вопросы?
Это я понял. Не пойму как реализовать по-нормальному.

Последний раз редактировалось Stilet; 21.12.2012 в 16:33.
Suby вне форума Ответить с цитированием
Старый 21.12.2012, 15:06   #8
EUGY
Форумчанин
 
Аватар для EUGY
 
Регистрация: 11.07.2010
Сообщений: 914
По умолчанию

Так что тут понимать. Выделил через new - один раз удалил через delete.
Если попытаться удалить второй раз уже освобожденный блок - вылетает с ошибкой. Поэтому при дублировании указателя на строку счетчик должен увеличиться на единицу, а в деструкторе, наоборот, декремент. И если он нулевой, только тогда вызов delete.
EUGY вне форума Ответить с цитированием
Старый 21.12.2012, 15:09   #9
Suby
Пользователь
 
Аватар для Suby
 
Регистрация: 03.11.2012
Сообщений: 89
По умолчанию

Цитата:
Сообщение от Abstraction Посмотреть сообщение
operator= - не конструктор. Он не изменяет количества существующих объектов.
Да, поэтому там обознаечен декремент счетчика, для целей наглядности удаления старых данных и инкремент счетчика при присваивании новых данных. По сути там всё осталось как и прежде.
Suby вне форума Ответить с цитированием
Старый 21.12.2012, 15:11   #10
Abstraction
Старожил
 
Аватар для Abstraction
 
Регистрация: 25.10.2011
Сообщений: 3,178
По умолчанию

Цитата:
Это я понял. Не пойму как реализовать по-нормальному.
Выделили новый блок - копируйте в него содержимое старого. В случае классов (вроде std::string) лучше положиться на их оператор присваивания, а то вдруг в их "внутренностях" ещё цепочки указателей:
Код:
*name = *(n.name);
//А лучше - сразу name = new std::string(*n.name), он задействует конструктор копирования
В случае "простых" (заведомо не содержащих адресов) блоков памяти, можно использовать memcpy().

Цитата:
Да, поэтому там обознаечен декремент счетчика, для целей наглядности удаления старых данных и инкремент счетчика при присваивании новых данных. По сути там всё осталось как и прежде.
А. Всё, вопрос снят, сразу не заметил.

Последний раз редактировалось Abstraction; 21.12.2012 в 15:13.
Abstraction вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Выделение памяти Dady 1992 Общие вопросы C/C++ 2 26.06.2012 18:45
Распределение памяти. Динамическое выделение памяти с++ Tolian92 Помощь студентам 8 14.05.2012 21:44
Конструктор копирования и оператор присваивания DenisS0 Общие вопросы C/C++ 4 05.12.2011 10:41
До выделение памяти MAKTE Общие вопросы C/C++ 4 20.05.2008 21:34