Я знаю, что конструкторы не "возвращают" ничего, но например если я звоню CMyClass *object = new CMyClass()
там какой-либо путь состоит в том, чтобы сделать объект быть ПУСТЫМ, если конструктор перестал работать? В моем случае у меня есть некоторые изображения, которые должны быть загружены и если бы чтение файла перестало работать, я хотел бы, чтобы это возвратило пустой указатель. Там какой-либо путь состоит в том, чтобы сделать это?
Заранее спасибо.
Я согласен со всеми остальными, что вы должны использовать исключения, но если вам действительно нужно использовать NULL по какой-то причине, сделайте конструктор приватным и используйте фабричный метод:
static CMyClass* CMyClass::create();
Это означает, что вы не сможете нормально конструировать экземпляры, и вы больше не сможете выделять их на стеке, что является довольно большим недостатком.
Лучше всего делать исключения.
Вы также можете проверить значение errno
, если вы программируете в среде Unix.
В Visual C ++ 6 поведение по умолчанию при нехватке памяти заключалось в том, что оператор new возвращал NULL, а не генерировал исключение. Это не было поведением, стандартизованным позже в C ++, и не было идиоматическим в современном C ++.
Но вы, конечно, можете создать версию оператора new, которая ведет себя таким образом, если хотите, или использовать вариант nothrow
: if (Foo * foo = new (std :: nothrow ) Фу) {...}
.
Вы можете использовать malloc
вместо new
, поскольку malloc
не генерирует исключения. Вам нужно будет проверить результат malloc
, прежде чем использовать указатель. Кроме того, если malloc
завершится успешно, вам придется инициализировать объект.
malloc
не вызывает конструктор объекта.
Можно ли вместо этого использовать статический фабричный метод? При преобразовании между типами я мог бы сделать общедоступный статический CMyClass Convert (оригинал) и вернуть null, если оригинал равен нулю. Однако вы, вероятно, все равно захотите генерировать исключения для недопустимых данных.
Конструкторы не возвращают значения. Они инициализируют объект, и единственный способ сообщить об ошибках - через исключение.
Обратите внимание, что конструктор не выполняет никакого управления памятью. Память выделяется извне, а затем вызывается конструктор для ее инициализации. И эта память может быть выделена динамически ( тип * x = новый тип;
), но она также может быть в стеке ( тип x;
) или подобъектом более сложного типа. . Во всех случаях, кроме первого, значение null вообще не имеет смысла.
Безвкусица.
Что ж, если вы действительно хотите это сделать, перегрузите new, вызовите new частный конструктор, который не выполняет инициализацию, выполните инициализацию в new и верните new null, если инициализация не удалась.
Вы не должны выполнять такую работу в конструкторе. Конструкторы должны выполнять абсолютный минимум работы, чтобы сделать объект пригодным для использования.
Фактически вы можете заставить new "возвращать" 0, используя std :: nothrow, но это только приводит к тому, что он возвращает 0, если выделение памяти не удается. Как только он дошел до вашего конструктора, вы уже не сможете получить то, что хотите.
Вы должны разделить проблемы в вашем классе. Конструктор почти никогда не должен (я хочу сказать, что период «никогда», но я оставлю место для редкого исключения, о котором не могу придумать) обрабатывать файлы, если только обработка файлов не является его исключительной ответственностью (например, fstream).
Вместо того, чтобы рассказывать вам, как заставить конструктор вернуть null или как подделать его, позвольте мне предложить альтернативу: предложить способ избежать создания исключения, например, путем отложенной инициализации или невыбрасывающего конструктора. Однако, как только вы это сделаете, вам нужно будет иметь способ проверить действительность и убедиться, что любая попытка использовать недопустимый экземпляр вызывает исключение. Другими словами, вы откладываете исключение, а не избегаете его полностью.
Вот как это сделать: У вас уже есть конструктор, который берет путь к файлу и загружает его, бросая на сбой. Переместите внутренности в метод Load, который принимает путь к файлу и возвращает bool, чтобы указать на успех.Затем измените конструктор так, чтобы он просто вызывает Load и выдает false. В поле Load убедитесь, что экземпляр немедленно возвращает значение false, если экземпляр правильно инициализирован. Затем добавьте деструктор по умолчанию и метод IsValid.
Пер Деннис: Теперь добавьте второй конструктор, который принимает логическое значение для управления возникновением исключения, и рассмотрите возможность понижения Load до private, и в этом случае вы также удалите конструктор по умолчанию.
Это дает вам все, что вы можете попросить, не создавая неподдерживаемый код. Это должно выглядеть примерно так:
// Per Dennis, should go away if Load becomes private.
Image()
{
_valid = false;
}
Image(const string& filepath)
{
if (!Load(filepath))
throw new exception("Cannot open image.");
}
// Per Dennis.
Image(const string& filepath, bool doThrow)
{
if (!Load(filepath) && doThrow)
throw new exception("Cannot open image.");
}
// Per Dennis, this should probably be made private now.
bool Load(const string& filepath)
{
if (_valid)
return false;
// Try to load...
_valid = WhetherItLoadedExpression;
return _valid;
}
bool IsValid()
{
return _valid;
}
void Draw()
{
if (!IsValid())
throw new exception("Invalid object.");
// Draw...
}
edit
См. ниже изменения, сделанные в ответ на комментарий Денниса.
Способ сделать это заключается в том, что если вы обнаружите, что что-то не работает в вашем конструкторе, вы должны выбросить исключение. Вот что происходит, если C++ не может выделить память для вашего объекта - он выбрасывает std::bad_alloc. Вы должны использовать std::exception или его подкласс.
"Правильный "** способ - выбросить исключение.
** Вы можете предоставить функцию-член типа is_valid
, которую можно проверить после конструирования объекта, но это просто не идиоматично в C++.