c++ это Сколько работы должно быть сделано в конструкторе?




метаданные сайта это (15)

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

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

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

Какое изящное решение?


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


Отличный вопрос: пример, который вы дали, где объект «Directory» имеет ссылки на другие объекты «Directory», также является отличным примером.

В этом конкретном случае я бы переместил код, чтобы создать подчиненные объекты из конструктора (или, возможно, сделать первый уровень [непосредственные дети], как рекомендует другое сообщение здесь), и иметь отдельный механизм «инициализировать» или «построить»).

В противном случае существует другая потенциальная проблема - помимо просто производительности - это объем памяти: если вы в конечном итоге сделаете очень глубокие рекурсивные вызовы, вы, скорее всего, также столкнетесь с проблемами памяти [поскольку в стеке будут храниться копии всех локальных переменных пока рекурсия не закончится].


Обобщить:

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

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

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

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

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

  • В целом, это зависит.


Убедитесь, что ctor ничего не делает, что может вызвать исключение.


Я бы согласился с тем, что длинные конструкторы не являются по своей сути плохими. Но я бы сказал, что твои дела почти всегда ошибаются. Мой совет подобен тому, что у Хьюго, Рича и Литба:

  1. сохраните работу, которую вы выполняете в конструкторах, до минимума - сосредоточьтесь на состоянии инициализации.
  2. Не бросайте из конструкторов, если вы не можете этого избежать. Я пытаюсь бросить только std :: bad_alloc.
  3. Не вызывайте OS или библиотеки API, если вы не знаете, что они делают - большинство может блокировать. Они быстро запускаются на вашем блоке dev и тестовых машинах, но в поле они могут быть заблокированы в течение длительных периодов времени, когда система занята чем-то другим.
  4. Никогда, никогда не делайте ввода-вывода в конструкторе - любого типа. Входы / выходы обычно подпадают под все виды очень длинных латентных значений (от 100 с миллисекунд до нескольких секунд). Ввод-вывод включает
    • Дисковый ввод-вывод
    • Все, что использует сеть (даже косвенно). Помните, что большинство ресурсов могут быть отключены.

Пример проблемы ввода-вывода. У многих жестких дисков есть проблема, когда они попадают в состояние, когда они не обслуживают чтение или запись за 100 или даже миллионы секунд. Первые и поколения твердотельных дисков делают это часто. У пользователя теперь есть возможность узнать, что ваша программа jus висела немного - они просто думают, что это ваше багги-программное обеспечение.

Конечно, злобность длинного конструктора зависит от двух вещей:

  1. Что означает «долго»
  2. Как часто в определенный период строятся объекты с длинными конструкторами.

Теперь, если «длинный» - это всего несколько 100 дополнительных тактовых циклов работы, то это не очень долго. Но конструктор попадает в 100-х микросекундный диапазон, я предлагаю, что он довольно длинный. Конечно, если вы только создаете экземпляр одного из них или создаете их редко (скажем, каждые несколько секунд), вы вряд ли увидите проблемы из-за продолжительности в этом диапазоне.

Частота является важным фактором, 500 000 долл. США не являются проблемой, если вы только строите несколько из них: но создание миллиона из них будет представлять значительную проблему с производительностью.

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

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

UI Hangs - это то, что может заставить людей действительно ненавидеть ваше программное обеспечение.

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


Столько, сколько необходимо и не больше.

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

Один из способов его реализации состоит в том, чтобы конструктор ничего не делал, с функцией-членом с циклом, который вычисляет необходимое значение и возвращает.

Другим способом его реализации было бы иметь переменную класса, которая является массивом. Конструктор установил бы все значения равными -1, чтобы указать, что значение еще не рассчитано. функция-член будет делать ленивую оценку. Он смотрит на элемент массива. Если он равен -1, он вычисляет его и сохраняет его и возвращает значение, иначе он просто возвращает значение из массива.

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

Другим способом его реализации было бы сохранить значения в текстовом файле и использовать N в качестве основы для смещения в файл, чтобы вывести значение. В этом случае конструктор откроет файл, и деструктор закроет файл, в то время как метод будет делать какой-то fseek / fread и вернуть значение.

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

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


Я голосую за тонких конструкторов и добавляю дополнительное «неинициализированное» поведение состояния к вашему объекту в этом случае.

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

Может быть трудно поймать ошибки из таких объектов, если они становятся статическими, потому что конструктор запускается до main () и сложнее отследить отладчик.


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

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


Массивы объектов всегда будут использовать конструктор по умолчанию (без аргументов). Это не так.

Существуют «специальные» конструкторы: конструктор копирования и оператор = ().

У вас может быть много конструкторов! Или закончите с большим количеством конструкторов позже. Время от времени Билл на ла-ла-земле хочет нового конструктора с плавающими, а не двойными, чтобы сохранить эти 4 паршивых байта. (Купите ОЗУ!)

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

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

,

Все это создает много горя, когда в конструкторе существует значительная логика. (Или, по крайней мере, дублирование кода.)

Поэтому я, как выбор дизайна, предпочитаю иметь минимальные конструкторы, которые (необязательно, в зависимости от их параметров и ситуации) вызывают метод initialize ().

В зависимости от обстоятельств, initialize () может быть закрытым. Или он может быть общедоступным и поддерживать множественные вызовы (например, повторная инициализация).

,

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

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


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


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

Для дерева каталогов, о котором вы говорите, «элегантное» решение, вероятно, не должно создавать полное дерево при построении объекта. Вместо этого постройте его по требованию. Кто-то, использующий ваш объект, может не беспокоиться о том, что находится в подкаталогах, поэтому начните с того, что ваш конструктор перечислит первый уровень, а затем, если кто-то захочет спуститься в конкретный каталог, затем создайте ту часть дерева, когда они запрашивают Это.


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

На вашем объекте структуры каталогов: Недавно я реализовал браузер samba (windows share) для моего HTPC, и поскольку это было невероятно медленным, я решил фактически инициализировать каталог, когда он был затронут. Например, сначала дерево состояло бы из всего списка машин, тогда всякий раз, когда вы просматриваете каталог, система автоматически инициализирует дерево с этого компьютера и получает каталог с одним уровнем глубже и т. д.

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


Это действительно зависит от контекста, то есть от проблемы, которую должен решить класс. Должен ли он, например, всегда быть в состоянии показать текущих детей внутри себя? Если да, то дети не должны загружаться в конструктор. С другой стороны, если класс представляет собой моментальный снимок структуры каталогов, он может быть загружен в конструктор.


RAII является основой управления ресурсами C ++, поэтому приобретайте необходимые ресурсы в конструкторе, отпустите их в деструкторе.

Это когда вы устанавливаете свои инварианты класса. Если это требует времени, требуется время. Чем меньше конструкций «if X exists do Y» у вас есть, тем проще будет дизайн остальных классов. Позже, если профилирование показывает, что это проблема, рассмотрите оптимизации, такие как ленивая инициализация (получение ресурсов, когда они вам в первую очередь нужны).





constructor