2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу 1, 2, 3  След.
 
 Как сделать, что бы new возвращал NULL?
Сообщение10.02.2006, 08:45 
Заслуженный участник
Аватара пользователя


12/10/05
478
Казань
Почитал тут на одном форуме - у парня траблы - оператор new возвращал NULL.
Мне-то как раз интересно, как это сделать самому, поскольку исключения я терпеть не могу.
Там в процессе этого обсуждения потихоньку всплывает, как это сделать, но мне до конца не ясно. Может, кто-нить может уточнить?

 Профиль  
                  
 
 
Сообщение10.02.2006, 10:12 
Заслуженный участник
Аватара пользователя


17/10/05
3709
:evil:
Существует, если я не ошибаюсь, два способа -- определить new глобально, или определить new в классе. В последнем случае классы могут иметь разные new, что иногда удобно -- можно брать память из разных пулов, например.

 Профиль  
                  
 
 
Сообщение10.02.2006, 10:38 
Супермодератор
Аватара пользователя


29/07/05
8248
Москва
Все это для меня очень странно. В книге Герберта Шилдта "Самоучитель С++" четко написано
Герберт Шилдт писал(а):
Если свободной памяти недостаточно для выполнения запроса (new), то так же, как и malloc() оператор new возвращает нулевой указатель.


Я все время считал именно так и пока никаких странностей с этим не замечал! Более того, мне кажется более естественным именно возвращение NULL, а не бросание исключения.

MSDN писал(а):
If there is insufficient memory for the allocation request, by default operator new returns NULL. You can change this default behavior by writing a custom exception-handling routine and calling the _set_new_handler run-time library function with your function name as its argument. For more details on this recovery scheme, see The operator new Function.

 Профиль  
                  
 
 
Сообщение10.02.2006, 10:48 
Заслуженный участник
Аватара пользователя


12/10/05
478
Казань
PAV писал(а):
Все это для меня очень странно. В книге Герберта Шилдта "Самоучитель С++" четко написано
Герберт Шилдт писал(а):
Если свободной памяти недостаточно для выполнения запроса (new), то так же, как и malloc() оператор new возвращает нулевой указатель.


Я все время считал именно так и пока никаких странностей с этим не замечал! Более того, мне кажется более естественным именно возвращение NULL, а не бросание исключения.

Да, это более естественно, но во-первых, это вроде как противоречит стандарту, а во-вторых - как быть, если у нас следующая ситуация (это конструктор):
Код:
TDataFile::TDataFile(const char* FileName)
{
   
    FILE *fid = fopen(FileName, "rb");
    if(!fid)
        return;//Надо как-то сообщить, что создание объекта закончилось неудачно.
////// и т.д.
}

Это, конечно, несколько надуманный пример... :oops:

 Профиль  
                  
 
 
Сообщение10.02.2006, 12:31 
Супермодератор
Аватара пользователя


29/07/05
8248
Москва
Я в таких случаях обычно предусматриваю условия, при которых объект находится в неправильном состоянии (типа как раз память не выделена), о чем можно узнать функцией-членом IsValid()

В любом случае исключение всегда можно кинуть.

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

Там, где я работаю, принят следующий метод. Конструктор любых сколько-нибудь сложных объектов выполняет лишь инициализацию всех необходимых параметров, как правило нулевыми значениями. При этом объект находится в невалидном состоянии. Далее должна быть вызвана функция Init(), которая уже проводит содержательные действия и действительно может привести к ошибке, о чем сообщит обычно возвращаемым параметром.

 Профиль  
                  
 
 
Сообщение10.02.2006, 13:00 


10/11/05
18
Пермь
Цитата:
Там, где я работаю, принят следующий метод. Конструктор любых сколько-нибудь сложных объектов выполняет лишь инициализацию всех необходимых параметров, как правило нулевыми значениями. При этом объект находится в невалидном состоянии. Далее должна быть вызвана функция Init(), которая уже проводит содержательные действия и действительно может привести к ошибке, о чем сообщит обычно возвращаемым параметром


Если не секрет, почему так принято? Если это С++, то можно исключение обрабатывать и в конструкторе.

 Профиль  
                  
 
 
Сообщение10.02.2006, 13:21 
Супермодератор
Аватара пользователя


29/07/05
8248
Москва
Kelvin писал(а):
Если не секрет, почему так принято? Если это С++, то можно исключение обрабатывать и в конструкторе.


Точно я сказать не могу, то было принято еще до меня и теперь уже традиция. Кроме того, я объективно не обладаю уровнем компетенции сотрудников, которые эту систему создавали. Убежден, что это решение было принято не "просто так", а в силу объективных причин.

Одна из возможных причин - что содержательная инициализация сложных объектов связана с загрузкой большого числа настраиваемых параметров из текстовых ini-файлов. Это происходит в специальной функции Load, которая работает сразу со многими объектами. Соответственно в целях отладки удобно разделять два действия: формальное создание объекта, которое не может привести к ошибке, и его содержательное создание, которое возможно надо проконтролировать.

 Профиль  
                  
 
 
Сообщение10.02.2006, 13:52 
Заслуженный участник
Аватара пользователя


12/10/05
478
Казань
PAV писал(а):
Kelvin писал(а):
Если не секрет, почему так принято? Если это С++, то можно исключение обрабатывать и в конструкторе.


Точно я сказать не могу, то было принято еще до меня и теперь уже традиция. Кроме того, я объективно не обладаю уровнем компетенции сотрудников, которые эту систему создавали. Убежден, что это решение было принято не "просто так", а в силу объективных причин.


Я тоже потихоньку к этому склоняюсь. Т.е. желательно, что бы все объекты имели конструкторы "по умолчанию" и желательно, конструкторы копирования. Первое приводит к единообразию способов создания классов, а второе помогает избегать случаев, подобных обсуждаемому тут

 Профиль  
                  
 
 
Сообщение10.02.2006, 14:16 
Заслуженный участник


31/12/05
1480
Sanyok писал(а):
Я тоже потихоньку к этому склоняюсь. Т.е. желательно, что бы все объекты имели конструкторы "по умолчанию" и желательно, конструкторы копирования.

А также оператор присваивания.

На самом деле, конечно, в современном C++ принято по-другому. Концепция RAII (Resource Acquisition is Initialization) предписывает захватывать ресурс (например, открывать файл) в конструкторе и освобождать (закрывать файл) в деструкторе. При этом получается чистый и ясный код без методов Init() и анализа кодов возврата каждой функции. При возникновении исключения автоматически освобождаются ресурсы от места бросания исключения до места его обработки, и поэтому ловить их можно именно там, где мы знаем, что с ними делать.

Но, к сожалению, реально в мире существуют два разных языка - C++ и C с классами. Несмотря на то, что стандарт у них один, код выглядит совершенно по-разному. И дописывать программу на C с классами в современном стиле - ужасно, лучше применять это в новом проекте.

 Профиль  
                  
 
 
Сообщение10.02.2006, 14:38 


13/09/05
153
Москва
Отложенная инициализация очень удобная вещь. Ребята из Microsoft так тоже думают -
CWnd и что-от него порождено имеют функции отложенной инициализации - Create() :))

Не всегда в момент создания существуют физически данные, которыми нужно инициалировать, да и просто вдруг в конструкторе есть что-то, что может пройти не всегда успешно.
Сначала создаем сам объект, потом инициализуем. Проще не придумаешь, когда нужно создать объект и есть вероятность косяка при его создании. Сначала создаем пустой объект, затем вызываем функцию типа BOOL Initialize() для проверки, что все сошлось:).
Код:
CWnd* pWnd = new CWnd;
if (!pWnd || !pWnd->Create(.......))
{
// тут ассерт, да еще в Create можно парочку, и код красивый и отлаживать удобно:))
     ASSERT(FALSE);
     delete pWnd;
}

 Профиль  
                  
 
 
Сообщение10.02.2006, 15:10 
Заслуженный участник


31/12/05
1480
VLarin писал(а):
Отложенная инициализация очень удобная вещь. Ребята из Microsoft так тоже думают

Думали. Когда писали MFC, исключений не было.
VLarin писал(а):
Не всегда в момент создания существуют физически данные, которыми нужно инициалировать

А зачем создавать объект, если он еще не нужен?
VLarin писал(а):
да и просто вдруг в конструкторе есть что-то, что может пройти не всегда успешно.

Для этого и придуманы исключения.

 Профиль  
                  
 
 
Сообщение10.02.2006, 15:32 


13/09/05
153
Москва
To tolstopuz:
Цитата:
А зачем создавать объект, если он еще не нужен?

Простой пример - объект CWnd, мембер другого CWnd (например CView), создается с пустыми параметрами при создании последнего, а потом, при первом обращении к объекту из CView происходит его инициализация, так как в момент создания CView хендл окна и прочая хрень не известна и несуществует в природе.

Цитата:
Для этого и придуманы исключения.

Я просто не фанат исключений, ну не нравятся они мне:))
Если можно все руками проверить, и что нужно сделать, то зачем исключения:).

 Профиль  
                  
 
 
Сообщение10.02.2006, 15:47 
Заслуженный участник
Аватара пользователя


12/10/05
478
Казань
tolstopuz писал(а):
VLarin писал(а):
да и просто вдруг в конструкторе есть что-то, что может пройти не всегда успешно.

Для этого и придуманы исключения.


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

Код:
class mainclass
{
private:
     Type1 *var1;
     Type2 *var2;
     Type3 *var3;
public:
     mainclass(void);
     ~mainclass();
};
//Реализация
mainclass::mainclass(void)
{
     var1 = new Type1;
     var2 = new Type2;
     var3 = new Type3; //Предположим, тут не хватило памяти и автоматически генерируется исключение.
}

mainclass::mainclass(void)
{
     delete var1;
     delete var2;
     delete var3;
}


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

 Профиль  
                  
 
 
Сообщение10.02.2006, 16:25 
Заслуженный участник


31/12/05
1480
VLarin писал(а):
Простой пример - объект CWnd, мембер другого CWnd (например CView), создается с пустыми параметрами при создании последнего, а потом, при первом обращении к объекту из CView происходит его инициализация, так как в момент создания CView хендл окна и прочая хрень не известна и несуществует в природе.

Да, MFC так спроектирована. Вообще MFC очень часто приводят как неиссякаемый источник примеров плохого дизайна.
CWnd может быть в двух состояниях - привязанный к реальному окну или пустой. Во-первых, таких классов немного и их наличие не заставляет переделывать дизайн всех остальных, а во-вторых, никто не мешает завести в них и конструктор с параметром для использования в большинстве простых ситуаций.
VLarin писал(а):
Я просто не фанат исключений, ну не нравятся они мне:))
Если можно все руками проверить, и что нужно сделать, то зачем исключения:).

Я-то думал, что техника придумана для того, чтобы избавить человека от ручного труда :)

Возьмем пример (из головы, но похожий на реальность):

Код:
try
{
  OracleConnection con("host=xxx;login=xxx;passwd=xxx");
  con.Open();
  OracleCommand cmd("select name, email, salary from employees", con);
  OracleDataReader reader(cmd.ExecuteReader());
  while(reader.Read())
  try
  {
    SendGreetings(reader.GetString(0), reader.GetString(1), reader.GetInt(2));
  }
  catch(GreetingsException& e)
  {
    LogException(e);
  }
  catch(ConversionException& e)
  {
    LogException(e);
  }
}
catch(Exception& e)
{
  LogException(e);
}

Как именно будет выглядеть проверка руками, если ошибки могут произойти:
1. Внутри Open - скажем, пароль не тот.
2. Внутри ExecuteReader - скажем, потерли таблицу.
3. Внутри Read - скажем, отвалилась сеть.
4. Внутри GetInt - скажем, тупой DBA забыл поставить NOT NULL на salary.
5. Внутри SendGreetings - скажем, лежит почтовый сервер.

 Профиль  
                  
 
 
Сообщение10.02.2006, 16:29 
Заслуженный участник


31/12/05
1480
Sanyok писал(а):
В этом случае зачастую при генерации исключения может получится так, что память останется невысвобожденной.

Не в этом случае, а в случае неправильного программирования. Выражать владение объектом посредством необернутого указателя - зло.
Код:
class mainclass
{
private:
     std::auto_ptr<Type1> var1;
     std::auto_ptr<Type2> var2;
     std::auto_ptr<Type3> var3;
public:
     mainclass();
};
//Реализация
mainclass::mainclass():
  var1(new Type1), var2(new Type2), var3(new Type3)
{
}

Кстати, этот пример регулярно дают на собеседованиях :)

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 39 ]  На страницу 1, 2, 3  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group