Подтвердить что ты не робот

Должен ли конструктор С++ выполнять настоящую работу?

Возможный дубликат:
Сколько работы должно выполняться в конструкторе?

Я борюсь с некоторыми советами, которые у меня есть в голове, но для которых я не могу вспомнить рассуждения.

Кажется, я кое-что вспомнил, прочитав несколько советов (не могу вспомнить источник), что конструкторы С++ не должны выполнять настоящую работу. Скорее, они должны инициализировать только переменные. Далее пояснялось, что настоящая работа должна выполняться каким-то методом init(), который будет вызываться отдельно после создания экземпляра.

Ситуация у меня есть класс, который представляет собой аппаратное устройство. Для меня логичный смысл, чтобы конструктор вызывал подпрограммы, которые запрашивали устройство, чтобы создать переменные экземпляра, описывающие устройство. Другими словами, когда новый экземпляр объекта создается, разработчик получает объект, который готов к использованию, не требуется отдельный вызов объекта- > init().

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

Просто пытаюсь выяснить, какие ошибки, которые я сейчас не рассматриваю, которые могли бы привести к такому совету.

4b9b3361

Ответ 1

Я помню, что Скотт Мейерс в More Effective С++ рекомендует не иметь лишнего конструктора по умолчанию. В этой статье он также коснулся использования методов, которые были использованы Init() для "создания" объектов. В принципе, вы внесли дополнительный шаг, который возлагает ответственность на клиента этого класса. Кроме того, если вы хотите создать массив указанных объектов, каждому из них придется вручную вызвать Init(). У вас может быть функция Init, которую конструктор может вызвать внутри для поддержания кода в порядке или для вызова объекта, если вы реализуете Reset(), но из опыта лучше удалить объект и воссоздать его, а не пытаться до reset его значения по умолчанию, если только объекты не создаются и не уничтожаются много раз в режиме реального времени (скажем, эффекты частиц).

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

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

Ответ 2

Одна из причин не выполнять "работу" в конструкторе заключается в том, что если из него выдается исключение, деструктор класса не будет вызван. Но если вы используете принципы RAII и не полагаетесь на свой деструктор, чтобы выполнять очистку, тогда я лучше не вводить метод, который не требуется.

Ответ 3

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

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

Вопрос, который вы должны задать себе:

Можно ли использовать объект без вызова метода init?

Если ответ на это "Нет", я буду делать все, что работает в конструкторе. В противном случае вам придется поймать ситуацию, когда пользователь создал экземпляр, но еще не инициализирован, и возвращает какую-то ошибку.

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

Ответ 4

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

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

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

Ответ 5

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

Если ваша конструкция допускает ошибки при реализации, кто-то будет делать эти ошибки. Мой друг Мерфи сказал мне, что;)

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

Ответ 6

Единственная реальная причина - Testability. Если ваши конструкторы полны "реальной работы", это обычно означает, что объекты могут быть созданы только в полностью инициализированном запущенном приложении. Это знак, которому объект/класс нуждается в дальнейшем разложении.

Ответ 7

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

Если вам не удается подключиться к устройству на другом конце ссылки, часто бывает, что "устройство" на вашем конце доступно и позже, если другой конец будет действовать вместе. Примерами являются сетевые подключения и т.д.

С другой стороны, если вы попытаетесь получить доступ к локальному аппаратным устройствам, которые не существуют и никогда не будут существовать в рамках вашей программы (например, графической карты, которой нет), то я думаю, что это случай, когда вы хочу знать это в конструкторе, чтобы конструктор мог бросать, и объект не может существовать. Если вы этого не сделаете, вы можете получить недопустимый объект и всегда будете такими. Бросок в конструкторе означает, что объект не будет существовать, и, следовательно, функции не могут быть вызваны этим объектом. Очевидно, что вам нужно знать проблемы с очисткой, если вы бросаете конструктор, но если вы не делаете этого в этом случае, вы, как правило, оказываете проверку на проверку всех функций, которые могут быть вызваны.

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

Ответ 8

Я бы хотел добавить свой собственный опыт там.

Я не буду говорить много о традиционном обсуждении. Конструктор /Init... например, Google рекомендации советуют против чего-либо в конструкторе, но это потому, что они советуют Исключениям и 2 работают вместе.

Я могу говорить о классе Connection, который я использую.

Когда класс Connection создается, он попытается фактически связать себя (по крайней мере, если не сконструирован по умолчанию). Если Connection не удается... объект все еще сконструирован, и вы не знаете об этом.

Когда вы пытаетесь использовать класс Connection, вы, таким образом, находитесь в одном из трех случаев:

  • никогда не был задан параметp > исключение или код ошибки
  • объект фактически подключен > fine
  • объект не подключен, он попытается подключиться > это преуспевает, отлично, это не получается, вы получаете исключение или код ошибки

Я думаю, что это очень полезно иметь и то, и другое. Однако это означает, что в каждом отдельном методе, фактически использующем соединение, вам нужно проверить, работает ли он.

Это стоит того, что из-за событий разъединения. Когда вы подключены, вы можете потерять соединение, не зная об этом объекте. Инкапсулируя самопроверку соединения в метод reconnect, который вызывается внутренне всеми методами, требующими рабочего соединения, вы действительно изолируете разработчиков от решения проблем... или, по крайней мере, столько, сколько сможете, поскольку, когда все не удается у вас нет другого решения, позволяющего им знать:)

Ответ 9

Лучше избегать выполнения "реальной работы" в конструкторе.

Если я устанавливаю подключения к базе данных, открываю файлы и т.д. внутри конструктора, и если при этом один из них вызывает исключение, это приведет к утечке памяти. Это может скомпрометировать ваше приложение безопасность исключений.

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

Приступая к вашему сценарию, если процедуры, запрашивающие устройство, не создают каких-либо исключений, и вы не собираетесь тестировать класс в изоляции, то есть, вероятно, предпочтительнее выполнять работу в конструкторе и избегать вызовов для этого дополнительного init.

Ответ 10

Есть несколько причин, по которым я бы использовал отдельный конструктор /init ():

  • Lazy/Delayed initialization. Это позволяет вам быстро и быстро создавать объект, быстро реагировать на пользователя и задерживать более длительную инициализацию для последующей или фоновой обработки. Это также является частью одного или нескольких шаблонов проектирования, связанных с пулами объектов многократного использования, чтобы избежать дорогостоящего распределения.
  • Не уверен, что у этого есть собственное имя, но, возможно, когда объект создан, информация о инициализации недоступна или не понятна тому, кто создает объект (например, создание массового генерического объекта). В другом разделе кода есть ноу-хау для его инициализации, но не создается.
  • В качестве личной причины деструктор должен иметь возможность отменить все, что сделал конструктор. Если это связано с использованием внутреннего init/deinit(), нет проблем, если они являются зеркальными отображениями друг друга.