Передача в конструктор класса структуры

Обновлено: 29.04.2024

Потому что пример в той статье полностью корректен, и автор статьи абсолютно прав. Вот этот пример:


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

Сравните со старым методом, когда передавали по const&: он однозначно хуже, потому что исключает вариант (3):


Альтернативный вариант с && также хуже, но в обратную сторону:


Если не боитесь комбинаторного взрыва, то можете дать шанс && (но зачем? реального выигрыша в скорости не будет никакого, оптимизатор не дремлет):


Или вот то же самое, только с шаблонами (но опять же, зачем?):


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

Напоследок пара вариантов, как делать НЕ СТОИТ:


И так тоже не надо:


Почему так происходит, каков фундаментальный принцип? Он прост: объект, как правило, должен ВЛАДЕТЬ своими свойствами.

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


Обратите внимание: std::move для shared_ptr совершенно легален, он исключает накладные расходы на блокировку счетчика ссылок shared_ptr в памяти (сотни циклов CPU) и на его инкремент. Он никак не влияет на время жизни объекта и остальные ссылки на него. Но (X) можно делать, конечно, только в случае, если ссылка psh в коде ниже нам больше не нужна.

Мораль: не используйте const& повально. Смотрите по обстоятельствам.

P.S.
Используйте <> вместо () при передаче параметров конструктора. Модно, современно, молодежно.

P.P.S.
В заключение еще одна вещь: std::move() на самом деле ничего не перемещает и сам по себе транслируется в ноль ассемблерных инструкций. Все, что делает std::move(), — это как бы навешивает специальный «липкий ярлык» на ссылку, превращая ее в && — rvalue reference. И дальше можно с этим ярлыком отдельно «мэтчить» тип параметра функции (например, иметь отдельную перегрузку функции для &&-параметра и отдельную для &-параметра). Смысл &&-ярлыка — дать возможность вызывающему коду сказать вызываемому: «если хочешь, ты можешь съесть значение по этой ссылке, оно мне больше не нужно; но только если съешь, косточки-то оставь, мне еще деструктор потом вызывать для оставшегося скелета». С тем же успехом можно было бы передавать и обычные &-ссылки (по ним же тоже можно объект «съесть»), но с && получается лучше семантика, т.к. не перепутаешь: где можно съесть, а где можно только понюхать.

В этой связи название std::move() следует признать крайне неудачным. Правильно было бы назвать его std::eat_me_if_you_want() или std::bon_appetit(). Но std::move() короче.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Конструкторы копии

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

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

При определении конструктора копирования необходимо также определить оператор присваивания копирования (=). Дополнительные сведения см. в разделе "Назначение " и " Копирование конструкторов" и операторов присваивания копирования.

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

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

Конструкторы перемещения

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

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

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

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

Дополнительные сведения о написании конструктора нетривиального перемещения см. в разделе "Конструкторы перемещения" и "Операторы присваивания перемещения" (C++).

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

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

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

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

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

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

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

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

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

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

Или передать целое значение функции, принимающей объект Box:

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

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

Порядок строительства

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

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

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

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

Выполняет весь код в теле функции.

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

Выходные данные будут выглядеть следующим образом.

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

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

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

Отменяется код в теле функции конструктора.

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

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

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

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

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

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

В следующем примере показано поведение 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 :

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

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

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

Структура readonly

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

Члены экземпляров readonly

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

Как правило, модификатор readonly применяется к следующим типам элементов экземпляров.

Можно также применить модификатор readonly к методам, переопределяющим методы, объявленные в System.Object.

Свойства и индексаторы.

Если необходимо применить модификатор readonly к методам доступа свойства или индексатора, примените его в объявлении свойства или индексатора.

Компилятор объявляет метод доступа get автоматически реализуемого свойства как readonly независимо от наличия модификатора readonly в объявлении свойства.

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

Обратимое изменение

Структура record

Инициализация структуры и значения по умолчанию

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

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

Наиболее распространенная ситуация, когда значения по умолчанию отображаются в массивах или в других коллекциях, где внутреннее хранилище содержит блоки переменных. В следующем примере создается массив из 30 TemperatureRange структур, каждый из которых имеет значение по умолчанию:

Все поля элементов структуры должны быть определенно назначены при его создании, так как struct типы хранят их данные напрямую. default Значение структуры определенно присвоило всем полям значение 0. Все поля должны быть определенно назначены при вызове конструктора. Вы инициализируете поля с помощью следующих механизмов:

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

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

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

В случае встроенных типов значения используйте соответствующие литералы, чтобы указать значение типа.

Ограничения при проектировании типа структуры

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

Передача переменных типа структуры по ссылке

Структура ref

  • Структура ref не может быть типом элемента массива.
  • Структура ref не может быть объявленным типом поля класса или структурой, отличной от ref .
  • Структура ref не может реализовывать интерфейсы.
  • Структура ref не может быть упакована в System.ValueType или System.Object.
  • Структура ref не может быть аргументом типа.
  • Переменная структуры ref не может быть зафиксирована лямбда-выражением или локальной функцией.
  • Переменную структуры ref нельзя использовать в методе async . Однако переменные структуры можно использовать ref в синхронных методах, например в методах, возвращающих Task или Task .
  • Переменную структуры ref нельзя использовать в итераторах.

Как правило, тип структуры ref определяется, если требуется тип, который также содержит члены данных типов структуры ref :

Чтобы объявить структуру ref как readonly , объедините модификаторы readonly и ref в объявлении типа (модификатор readonly должен предшествовать модификатору ref ):

Ограничение struct

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

Преобразования

Для любого типа структуры (за исключением типов структуры ref ) существует упаковка-преобразование и распаковка-преобразование в типы System.ValueType и System.Object и из них. Существуют упаковка-преобразование и распаковка-преобразование между типом структуры и любым интерфейсом, который он реализует.

I have been trying to solve this problem but I am not getting the syntax.

16 Answers 16

You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers.

Trending is based off of the highest score sort and falls back to it if no posts are trending.

In C++ the only difference between a class and a struct is that members and base classes are private by default in classes, whereas they are public by default in structs.

So structs can have constructors, and the syntax is the same as for classes.

@sth Your right on the difference between struct and class, however I think he's having a compile issue. The issue might be because of a union that is using the struct. You can't have non-trivial constructors in the type you have in a union.

@Chap: If he has concrete problems where the general solution doesn't work, it would probably be the best idea to post some code that shows the problem and the compiler errors that are generated. But as general as the question is asked I don't think one can really infer too much about the concrete problem the OP is trying to solve.

@GMan: Right idea, wrong wording. A struct inherits its base classes publicly by default; there is no change to classes deriving from the struct .

@BenVoigt: Whoa. How'd you find this old comment. :) Yeesh wish I could edit it. even I'm confused at what I wrote. I think I omitted the word "bases" from the end but even that sucks.

@user152949: No one said it would. We could all comment on all code excerpts saying 'This won't work if [some totally different scenario]', but what's the point?

@varungupta It's the body of the constructor function. There's no code we want to run in the constructor in this case, so it's empty.

All the above answers technically answer the asker's question, but just thought I'd point out a case where you might encounter problems.

If you declare your struct like this:

You will have problems trying to declare a constructor. This is of course because you haven't actually declared a struct named "foo", you've created an anonymous struct and assigned it the alias "foo". This also means you will not be able to use "foo" with a scoping operator in a cpp file:

To fix this, you must either do this:

Where the latter creates a struct called "foo" and gives it the alias "foo" so you don't have to use the struct keyword when referencing it.

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

Вот простой пример, как вы ответите на следующий вопрос: сколько значимых типов из .NET Framework содержит конструкторы по умолчанию? Интуитивным ответом кажется "все", и будете не правы, поскольку на самом деле, ни один из значимых типов .NET Framework не содержит конструктора по умолчанию.


Но давайте обо всем по порядку и начнем со сравнения таких понятий, как конструктор по умолчанию (default constructor) и значения по умолчанию (default values).

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

Аналогичное смешивание понятий существует не только при вызове оператора new, но и при инициализации полей структуры в конструкторе. Так, для конструкторов структур применяются те же правила обязательной инициализации всех полей структуры, аналогичные правилам для локальных переменных (definite assignment rules). Это означает, что до завершения тела конструктора все поля структуры должны быть явно или неявно проинициализированы:

Вызов this() выглядит в точности как вызов конструктора по умолчанию и предотвращает ошибку компиляции за счет того, что он обнуляет (а значит и инициализирует) все поля структуры. На самом же деле вызов this() превращается все в ту же инструкцию initobj, используемую ранее для получения значения по умолчанию экземпляра структуры.

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

Оба типа Size и CustomValueType являются значимыми типами, при этом тип CustomValueType содержит конструктор по умолчанию (мы рассмотрим позднее, как этого добиться). В строке 2 происходит инициализация переменной size значениями по умолчанию, с помощью инструкции initobj, а в строке 3 происходит вызов конструктора типа CustomValueType с помощью инструкции call CustomValueType..ctor.

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

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

Метод GenerateValueTypeWithDefaultConstructor принимает имя типа и строку, выводимую на консоль (строка нужна для написания модульного теста, проверяющего работу этого метода). После этого, мы генерируем простой значимый тип с конструктором, вызывающим Console.WriteLine с указанной строкой.
После этого мы можем создать данный тип с помощью Activator.CreateInstance или же сохранить сгенерированную сборку с помощью AssemblyBuilder.Save и использовать ее обычным образом. После этого, можно будет пользоваться сгенерированным типом обычным образом и спокойно вызвать конструктор по умолчанию значимого типа:

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

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

Для создания массива используется инструкция newarr, при этом происходит инициализация всех элементов массива значениями по умолчанию, что в случае значимых типов означает «обнуление» всех полей всех экземпляров массива. Даже если используемый тип содержит настоящий конструктор по умолчанию (как в нашем случае), в интересах производительности они все равно вызваны не будут.

Для вызова конструктора всех элементов нужно сделать это явно:

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


Хочу обратить внимание на рассогласованность поведения в следующем случае: известно, что создания экземпляра обобщенного (generic) параметра происходит с помощью Activator.CreateInstance, именно поэтому при возникновении исключения в конструкторе пользователь обобщенного метода получит его не в чистом виде, а в виде TargetInvocationException. Однако при создании экземпляра типа CustomValueType с помощью Activator.CreateInstance наш конструктор по умолчанию будет вызван, а при вызове метода CreateWithNew и создания экземпляра значимого типа с помощью new T() – нет.

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