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

Обновлено: 21.11.2024

О до-диезе

Об этом учебнике по программированию на C Sharp

Если вы не знаете, является ли используемый вами объект типом значения или ссылочным типом, вы можете столкнуться с некоторыми неожиданностями. Например:

Как видите, объекты Point и Pen были созданы одинаково, но значение point1 осталось неизменным, когда новое значение координаты X было присвоено point2 , тогда как значение Pen1 было изменено, когда для pen2 был назначен новый цвет. Таким образом, мы можем вывести, что точки point1 и point2 содержат собственную копию объекта Point, тогда как pen1 и pen2 содержат ссылки на один и тот же объект Pen. Но как мы можем узнать это, не проведя этот эксперимент?

Ответ заключается в том, чтобы просмотреть определения типов объектов (что можно легко сделать в Visual Studio, поместив курсор на имя типа объекта и нажав F12):

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

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

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

Первая сигнатура метода (т. е. без параметра compareType) фактически аналогична использованию оператора ==, но имеет то преимущество, что она явно применяется к строкам. Он выполняет порядковое сравнение строк, которое в основном представляет собой побайтовое сравнение. Во многих случаях это именно тот тип сравнения, который вам нужен, особенно при сравнении строк, значения которых устанавливаются программно, таких как имена файлов, переменные среды, атрибуты и т. д. В этих случаях, если порядковое сравнение действительно является правильным типом сравнения в этой ситуации, единственным недостатком использования метода Equals без compareType является то, что кто-то, читающий код, может не знать, какой тип сравнения вы делаете.

Однако использование сигнатуры метода Equals, которая включает в себя compareType при каждом сравнении строк, не только сделает ваш код более понятным, но и заставит вас явно задуматься о том, какой тип сравнения вам нужно выполнить. Это стоит сделать, потому что даже если английский язык не может обеспечить много различий между порядковыми и учитывающими культуру сравнениями, другие языки предоставляют множество, и игнорирование возможностей других языков открывает перед вами большой потенциал для ошибки в пути. Например:

Самая безопасная практика — всегда указывать параметр compareType в методе Equals. Вот несколько основных рекомендаций:

  • При сравнении строк, которые были введены пользователем или должны отображаться пользователю, используйте сравнение с учетом языка и региональных параметров ( CurrentCulture или CurrentCultureIgnoreCase ).
  • При сравнении программных строк используйте порядковое сравнение ( Ordinal или OrdinalIgnoreCase ).
  • InvariantCulture и InvariantCultureIgnoreCase обычно не следует использовать, за исключением очень ограниченного числа случаев, поскольку порядковые сравнения более эффективны. Если необходимо сравнение с учетом культуры, его обычно следует выполнять с текущей культурой или другой конкретной культурой.

можно просто написать:

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

Например, рассмотрим следующее утверждение:

Что произойдет, если один из объектов account.Status будет равен «Активный» (обратите внимание на заглавную букву А)? Ну, если бы myAccounts был объектом DbSet (который был настроен с конфигурацией по умолчанию без учета регистра), выражение where все равно соответствовало бы этому элементу. Однако если бы myAccounts находились в массиве в памяти, они бы не совпадали и, следовательно, давали бы другой общий результат.

Но подождите минутку. Когда мы говорили о сравнении строк ранее, мы видели, что оператор == выполняет порядковое сравнение строк. Так почему же в этом случае оператор == выполняет сравнение без учета регистра?

Как упоминалось ранее, операторы LINQ работают с любым объектом, который реализует IEnumerable. Например, следующая простая функция суммирует балансы на любом наборе счетов:

Ответ заключается в том, что Sum() не является методом, определенным в интерфейсе IEnumerable. Скорее, это статический метод (называемый «метод расширения»), определенный в классе System.Linq.Enumerable:

Так чем же метод расширения отличается от любого другого статического метода и что позволяет нам получить к нему доступ в других классах?

Отличительной характеристикой метода расширения является модификатор this в его первом параметре. Это «волшебство», которое идентифицирует его для компилятора как метод расширения. Тип параметра, который он изменяет (в данном случае IEnumerable ), обозначает класс или интерфейс, который затем появится для реализации этого метода.

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

При таком понимании мы также можем видеть, что функцию sumAccounts, которую мы представили выше, можно было бы вместо этого реализовать следующим образом:

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

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

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

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

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

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

Создавая блок using в приведенном выше примере, вы точно знаете, что myFile.Dispose() будет вызываться, как только вы закончите работу с файлом, независимо от того, генерирует ли Read() исключение.

Самой очевидной ошибкой, которая может возникнуть при использовании метода 2, будет невозможность проверить возвращаемое значение. Это, вероятно, приведет к возможному исключению NullReferenceException, которое может появиться гораздо позже, что значительно усложнит отслеживание источника проблемы. В отличие от этого, метод 1 немедленно вызвал бы исключение InvalidCastException, что сделало бы источник проблемы более очевидным.

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

Вот несколько примеров других распространенных пар методов, где один генерирует исключение, а другой нет:

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

Однако неверно предполагать, что TryParse обязательно является «лучшим» методом. Иногда это так, иногда нет. Вот почему есть два способа сделать это.Используйте правильный вариант для контекста, в котором вы находитесь, помня, что исключения, безусловно, могут быть вашими друзьями как разработчика.

Но если вы проигнорируете предупреждение такого типа, рано или поздно что-то вроде этого вполне может попасть в ваш код:

А с той скоростью, с которой Intellisense позволяет нам писать код, эта ошибка не так уж невероятна, как кажется.

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

Помните, что компилятор C Sharp предоставляет много полезной информации о надежности вашего кода… если вы слушаете. Не игнорируйте предупреждения. Обычно их исправление занимает всего несколько секунд, а исправление новых, когда они происходят, может сэкономить вам часы. Приучите себя ожидать, что в окне «Список ошибок» Visual Studio отобразится «0 ошибок, 0 предупреждений», чтобы любые предупреждения вызывали у вас достаточно дискомфорта, чтобы вы могли немедленно их устранить.

Подведение итогов

Дополнительная литература в блоге Toptal Engineering:

Понимание основ

Хотите улучшить этот вопрос? Обновите вопрос, чтобы на него можно было ответить фактами и цитатами, отредактировав этот пост.

Закрыт 6 лет назад.

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

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

С другой стороны, это выглядит немного странно. Более того, в этом выступлении примерно в 17:58 есть веские причины не делать много работы в конструкторе. Я думаю, что могу устранить проблему, передав соответствующие манекены в качестве аргументов конструктора.

Остается вопрос: плохо ли выполнять большую часть работы (или даже всю работу) в конструкторах?

5 ответов 5

Я думаю, что "Выполнение работы в конструкторе" нормально.

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

  • Это затрудняет тестирование
    • Все примеры, которые я видел, были связаны с тем, что DI не использовался. На самом деле это не вина конструктора, выполнявшего реальную работу.
    • По сути, это нарушение SRP, а не ошибка конструктора, выполняющего работу за свой счет.
    • Я не думаю, что стоит писать новый код с учетом исторических недостатков компилятора. С тем же успехом мы могли бы покончить с C++11 и всем, что хорошо вместе, если мы это сделаем.

    Мое мнение таково.

    <р>. если ваш конструктор должен выполнять работу, чтобы он соответствовал принципу «Инициализация ресурсов — это инициализация» (RAII), и класс не нарушает SRP, а DI используется правильно; Тогда работа в конструкторе — это хорошо! Вы даже можете создать исключение, если хотите предотвратить использование объекта класса, инициализация которого полностью завершилась неудачно, вместо того, чтобы полагаться на то, что пользователь проверит какое-либо возвращаемое значение.

    "возвращаемые значения" будут через общедоступный член, потому что конструкторам не разрешено возвращать значение.

    @v.oddou Или через аргумент, переданный (по ссылке) конструктору. Но если конструктор действительно является функцией, это же правило должно применяться ко ВСЕМ другим функциям. Это начинает походить на функциональное программирование, если вы работаете с языком FP.

    Это очень открытый вопрос, поэтому мой ответ будет максимально общим.

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

    При этом вы заметите, что он также касается внедрения зависимостей/паттерна провайдера, который печально известен тем, что усложняет конструкторы. В таком случае предпочтительнее оставить ТОЛЬКО код провайдера/DI в конструкторе. Опять же, ответ зависит от того, какие шаблоны вы используете и как ваш код «сочетается» друг с другом.

    Вся суть использования конструктора заключается в создании аккуратного объекта, который можно использовать немедленно; то есть новый Студент("Дэвид Титаренко", "Старший", 3.5) . Нет необходимости выполнять david.initialize(), так как это было бы совершенно глупо.

    Вот, например, часть моего производственного кода:

    В приведенном выше случае я решил ничего не делать с конструктором (он пустой), а использовать метод doConfig(), который выполняет весь дисковый ввод-вывод; Я часто думал, что метод doConfig() просто бесполезен, и я должен делать все в конструкторе. (В конце концов, я проверяю файл конфигурации только один раз.)

    Я думаю, что это полностью зависит от вашего кода, и вам не следует думать, что добавление "вещей" в ваш конструктор - это плохо. Вот для чего нужны конструкторы! Иногда мы увлекаемся ООП ( getThis , setThat , doBark ), когда на самом деле все, что нужно сделать классу, это загрузить файл конфигурации. В таких случаях просто поместите все в конструктор и закройте его!

    В вашем примере конфигурации есть недостатки, поскольку он нарушает SRP. Что, если бы вы могли хранить конфигурацию в СУБД, а не в файле? Config config = new FileConfigurationProvider('server.conf').getConfig(); . и назовите это днем! Никакой работы в конструкторах и лучшего дизайна.

    Ах, все эти "а что если", которые никогда не случаются. Не переусердствуйте с кодом. Вот для чего нужны рефакторинги.

    Для небольших фрагментов кода примеров, да, закройте день и делайте что угодно. Но в более крупных производственных кодовых базах это просто скелеты в шкафу. Так что это решение прекрасно, пока какой-нибудь коллега не начнет вызывать «new Config()» в случайных частях кода, вызывая некоторые дисковые операции ввода-вывода и несколько экземпляров объектов конфигурации.

    Когда я помещал слишком много кода в конструктор, я столкнулся со следующими проблемами:

    • Было трудно писать модульные тесты для других методов этого класса, потому что в конструкторе нужно было делать много вещей, поэтому мне приходилось настраивать множество допустимых вещей или, по крайней мере, имитации (БД, файл , что угодно) для простейших модульных тестов.
    • Было сложно писать модульные тесты для самого конструктора. В любом случае помещать много кода с разноплановыми обязанностями в один блок — даже плохая идея. (Принцип единой ответственности.)
    • По прежним причинам использовать этот класс было сложно. Например, он совершенно не позволял мне реализовать некоторые методы отложенной инициализации, потому что ему нужно было все в момент вызова конструктора. Хорошо, я мог бы написать ленивый метод инициализации в конструкторе, отлично.
    • Рано или поздно я понял, что есть смысл переиспользовать некоторые части кода, размещенные в конструкторе. Что ж, когда я впервые написал конструктор, я тоже думал, что эти части кода будут использоваться только там, навсегда.
    • Когда я хотел расширить этот класс и вставить некоторую логику до или в логику конструктора супера, это просто не сработало, потому что первое, что нужно сделать в конструкторе расширенного класса, — это вызвать конструктор супера.

    Поэтому да, делать много в конструкторе — плохая идея, на мой взгляд.

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

    Я думаю, что ваши проблемы могли быть вызваны нарушением SRP, а не фактической работой конструктора.

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

    Думаю, главная мысль, которую я хочу подчеркнуть, заключается в том, что «проверка на будущее» — это плохая практика. Решение текущей проблемы самым простым способом является хорошей практикой. Таким образом, «угадывание» того, как что-то может быть повторно использовано, в корне не является хорошей практикой. Если создание абстракции (метода, класса и т. д.) улучшает читаемость или «известный» сценарий повторного использования, который вы собираетесь использовать «прямо сейчас», отлично, сделайте это. например Если я увижу конструктор из 100 строк, я скажу: "Это нечитабельно, разбейте его на логические шаги/приватные методы"

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

    Как только вы начнете использовать Builders и Factory для создания своих объектов, они смогут проверить предварительные и последующие условия и убедиться, что клиенты вашего кода смогут получить доступ только к полностью инициализированному объекту, а не к наполовину созданному, вы даже можете используйте современные модные интерфейсы Fluent, чтобы создать свой объект и придать ему крутой вид ;D

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

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

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

    другой пример: допустим, у вас есть представление, которое загружает 20 изображений. Но показаны только 5, и конструктор берет массив изображений для отображения. Вы можете загрузить их все в конструкторе, но с точки зрения пользователя поначалу это будет казаться медленным. Или вы можете загрузить 1 «загрузочное» изображение и загрузить 1 изображение за раз. И по мере прокрутки пользователем загружайте больше изображений в зависимости от показанного/необходимого.

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

    В Java конструктор представляет собой блок кода, аналогичный методу. Он вызывается при создании экземпляра класса. В момент вызова конструктора в памяти выделяется память под объект. Это особый тип метода, который используется для инициализации объекта. Каждый раз, когда объект создается с помощью ключевого слова new(), вызывается как минимум один конструктор.

    Примечание. Нет необходимости писать конструктор для класса. Это связано с тем, что компилятор Java создает конструктор по умолчанию, если в вашем классе его нет.

    Чем конструкторы отличаются от методов в Java?

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

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

    Необходим конструктор

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

    Когда вызывается конструктор?

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

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

    На данный момент мы знаем, что конструкторы используются для инициализации состояния объекта. Как и методы, конструктор также содержит набор операторов (т. е. инструкций), которые выполняются во время создания объекта.

    Типы конструкторов в Java

    • Конструктор без аргументов
    • Параметрический конструктор
    <р>1. Конструктор без аргументов

    Конструктор без параметров называется конструктором по умолчанию. Если мы не определяем конструктор в классе, то компилятор создает конструктор по умолчанию (без аргументов) для класса. А если мы напишем конструктор с аргументами или без аргументов, то компилятор не создаст конструктор по умолчанию.

    Примечание. Конструктор по умолчанию предоставляет значения по умолчанию для объекта, такие как 0, null и т. д., в зависимости от типа.

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

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

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

    • Конструкторы могут быть объявлены как встроенные, явные, дружеские или constexpr.
    • Конструктор может инициализировать объект, объявленный как const , volatile или const volatile . Объект становится константным после завершения конструктора.
    • Чтобы определить конструктор в файле реализации, присвойте ему полное имя, как и любой другой функции-члену: Box::Box() .

    Списки инициализаторов элементов

    Конструктор может дополнительно иметь список инициализаторов членов, который инициализирует члены класса до запуска тела конструктора. (Список инициализаторов членов — это не то же самое, что список инициализаторов типа std::initializer_list .)

    Предпочитайте списки инициализаторов элементов, а не присвоение значений в теле конструктора. Список инициализаторов элементов непосредственно инициализирует элементы. В следующем примере показан список инициализаторов элементов, который состоит из всех выражений идентификатора (аргумента) после двоеточия:

    Идентификатор должен относиться к члену класса; он инициализируется значением аргумента. Аргументом может быть один из параметров конструктора, вызов функции или std::initializer_list .

    Члены-константы и члены ссылочного типа должны быть инициализированы в списке инициализаторов членов.

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

    Конструкторы по умолчанию

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

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

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

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

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

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

    Это утверждение является примером проблемы "Самый неприятный анализ". Вы можете интерпретировать myclass md(); либо как объявление функции, либо как вызов конструктора по умолчанию. Поскольку синтаксические анализаторы C++ предпочитают объявления другим вещам, выражение рассматривается как объявление функции. Дополнительную информацию см. в разделе Самый неприятный разбор.

    Если объявлены какие-либо конструкторы не по умолчанию, компилятор не предоставляет конструктор по умолчанию:

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

    Однако вы можете использовать набор списков инициализаторов для инициализации массива объектов Box:

    Дополнительную информацию см. в разделе Инициализаторы.

    Конструкторы копирования

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

    Конструктор копирования может иметь одну из следующих сигнатур:

    Когда вы определяете конструктор копирования, вы также должны определить оператор присваивания копии (=). Дополнительные сведения см. в разделе Конструкторы присваивания и копирования, а также операторы присваивания копирования.

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

    При попытке скопировать объект возникает ошибка C2280: попытка сослаться на удаленную функцию.

    Перемещение конструкторов

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

    Компилятор выбирает конструктор перемещения, когда объект инициализируется другим объектом того же типа, если другой объект вот-вот будет уничтожен и больше не нуждается в его ресурсах. В следующем примере показан один случай, когда конструктор перемещения выбирается с помощью разрешения перегрузки. В конструкторе, который вызывает get_Box() , возвращаемое значение представляет собой xvalue (значение eXpiring). Он не назначен какой-либо переменной и поэтому вот-вот выйдет за рамки. Чтобы мотивировать этот пример, давайте дадим Box большой вектор строк, представляющих его содержимое. Вместо того, чтобы копировать вектор и его строки, конструктор перемещения «крадет» его из «коробки» с истекающим значением, так что теперь вектор принадлежит новому объекту. Вызов std::move — это все, что нужно, поскольку и векторные, и строковые классы реализуют свои собственные конструкторы перемещения.

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

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

    Дополнительную информацию о том, как написать нетривиальный конструктор перемещения, см. в разделе Конструкторы перемещения и операторы присваивания перемещения (C++).

    Явно заданные по умолчанию и удаленные конструкторы

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

    конструкторы constexpr

    Конструктор может быть объявлен как constexpr, если

    • либо он объявлен как используемый по умолчанию, либо удовлетворяет всем условиям для функций constexpr в целом;
    • класс не имеет виртуальных базовых классов;
    • каждый из параметров является буквальным типом;
    • тело не является блоком проверки функции;
    • инициализируются все нестатические элементы данных и подобъекты базового класса;
    • если класс является (а) объединением с вариантными членами или (б) анонимными объединениями, инициализируется только один из членов объединения;
    • каждый нестатический член данных типа класса и все подобъекты базового класса имеют конструктор constexpr

    Конструкторы списка инициализаторов

    Если конструктор принимает std::initializer_list в качестве параметра, а любые другие параметры имеют аргументы по умолчанию, этот конструктор выбирается в разрешении перегрузки при создании экземпляра класса посредством прямой инициализации. Вы можете использовать initializer_list для инициализации любого члена, который может его принять. Например, предположим, что класс Box (показанный ранее) имеет член std::vector m_contents. Вы можете предоставить такой конструктор:

    А затем создайте объекты Box следующим образом:

    Явные конструкторы

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

    Бокс можно инициализировать следующим образом:

    Или передайте int функции, которая принимает Box:

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

    Если конструктор является явным, эта строка вызывает ошибку компилятора: ShippingOrder so(42, 10.8); . Дополнительную информацию см. в разделе Пользовательские преобразования типов.

    Порядок построения

    Конструктор выполняет свою работу в следующем порядке:

    Он вызывает базовый класс и конструкторы-члены в порядке объявления.

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

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

    Он выполняет любой код в теле своей функции.

    В следующем примере показан порядок, в котором конструкторы базового класса и членов вызываются в конструкторе производного класса. Сначала вызывается базовый конструктор. Затем члены базового класса инициализируются в том порядке, в котором они появляются в объявлении класса. Наконец, вызывается производный конструктор.

    Вот результат:

    Конструктор производного класса всегда вызывает конструктор базового класса, чтобы он мог полагаться на полностью сконструированные базовые классы, прежде чем будет выполнена какая-либо дополнительная работа. Конструкторы базового класса вызываются в порядке наследования — например, если ClassA является производным от ClassB , который является производным от ClassC , сначала вызывается конструктор ClassC, затем конструктор ClassB, затем конструктор ClassA.

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

    Если конструктор выдает исключение, порядок уничтожения является обратным порядку построения:

    Код в теле функции-конструктора раскручен.

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

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

    Производные конструкторы и расширенная агрегатная инициализация

    Если конструктор базового класса не является общедоступным, но доступен для производного класса, вы не можете использовать пустые фигурные скобки для инициализации объекта производного типа в режиме /std:c++17 и позже в режиме Visual Studio 2017 и более поздние версии.

    В следующем примере показано поведение, совместимое с C++14:

    В C++17 Derived теперь считается агрегатным типом. Это означает, что инициализация Base через закрытый конструктор по умолчанию происходит напрямую, как часть расширенного правила агрегатной инициализации. Ранее закрытый конструктор Base вызывался через конструктор Derived, и он завершался успешно из-за объявления друга.

    В следующем примере показано поведение C++17 в Visual Studio 2017 и более поздних версиях в режиме /std:c++17:

    Конструкторы для классов с множественным наследованием

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

    Вы должны ожидать следующий вывод:

    Делегирование конструкторов

    Делегирующий конструктор вызывает другой конструктор в том же классе, чтобы выполнить часть работы по инициализации. Эта функция полезна, когда у вас есть несколько конструкторов, каждый из которых должен выполнять одинаковую работу. Вы можете написать основную логику в одном конструкторе и вызывать ее из других. В следующем тривиальном примере Box(int) делегирует свою работу Box(int,int,int):

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

    Наследование конструкторов (C++11)

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

    Visual Studio 2017 и более поздние версии: оператор using в режиме /std:c++17 и более поздних версиях включает в область действия все конструкторы из базового класса, кроме тех, которые имеют идентичную сигнатуру конструкторов в производном классе. В общем, лучше всего использовать наследующие конструкторы, когда производный класс не объявляет новых элементов данных или конструкторов.

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

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

    Конструкторы и составные классы

    Классы, содержащие члены типа класса, называются составными классами. Когда создается член типа класса составного класса, конструктор вызывается перед собственным конструктором класса.Если в составном классе отсутствует конструктор по умолчанию, вы должны использовать список инициализации в конструкторе составного класса. В более раннем примере StorageBox, если вы измените тип переменной-члена m_label на новый класс Label, вы должны вызвать как конструктор базового класса, так и инициализировать переменную m_label в конструкторе StorageBox:

    Читайте также: