Ошибка при вызове определения конструктора ws 1c

Обновлено: 30.06.2024

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

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

Обязательные конструкторы uvm_object
На этой странице описываются важные действия, которые необходимо выполнить пользователям до следующего выпуска UVM.
Почему конструкторы uvm_object теперь обязательныUVM рекомендует указывать следующий конструктор для любого класса, расширенного из uvm_object:< /td>
Что пользователи должны сделать как можно скорееВ UVM 1.1a поведение фабрики объектов UVM по умолчанию сохраняет свое прежнее (если неправильное) поведение .
Что произойдет в UVM 1.2?В UVM 1.2 корректное поведение фабрики объектов UVM будет включено по умолчанию.
Что произойдет в UVM 1.3?В UVM 1.3 фабрика объектов UVM будет иметь ТОЛЬКО исправленное поведение.

Почему конструкторы uvm_object теперь обязательны

UVM рекомендует указывать следующий конструктор для любого класса, расширенного от uvm_object:

Однако в UVM 1.0 и UVM 1.1 наличие такого конструктора не обязательно в библиотеке, и технически они необязательны. Если конструктор не указан, SystemVerilog предоставляет конструктор по умолчанию:

Обратите внимание на важное отличие: в конструкторе по умолчанию нет аргумента name. Поскольку фабрика не могла полагаться на наличие этого аргумента, экземпляры uvm_object создавались путем вызова new() без каких-либо аргументов и последующей установки имени экземпляра объекта с помощью uvm_object::set_name.

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

будет наблюдаться разница в поведении при непосредственном создании экземпляра объекта:

и создание экземпляра объекта через фабрику:

Разницу часто можно обойти, переопределив метод uvm_object::set_name(), чтобы отразить эффект изменения имени. Однако этот обходной путь часто может быть невозможным или слишком запоздалым. В приведенных выше примерах первый создаст экземпляр your_obj с именем «o.sub», тогда как второй создаст экземпляр с именем «my_obj.sub». Это сделает невозможным управление фабрикой подобъектов на основе имени родительского объекта.

Создание группы покрытия — еще один пример, когда обходной путь не работает. Группы покрытия не могут быть переименованы и должны быть созданы в конструкторе инкапсулирующего объекта. В следующем примере создаются группы покрытия, которые всегда называются «my_obj» при использовании фабрики объектов.

Поскольку фабрика объектов является фундаментальным элементом UVM, важно, чтобы она могла полагаться на наличие аргумента name в конструкторах объектов.

Что пользователи должны сделать как можно скорее

В UVM 1.1a поведение фабрики объектов UVM по умолчанию сохраняет свое предыдущее (если неправильное) поведение. На данный момент никаких изменений не требуется. Однако поведение фабрики объектов UVM по умолчанию изменится в следующем выпуске. Поэтому важно, чтобы пользователи как можно скорее обновили свой код, чтобы быть готовыми к переходу на UVM 1.2, когда он станет доступен.

Правильное поведение фабрики можно включить, определив символ `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR при компиляции библиотеки UVM.

Включение исправленного поведения теперь требует, чтобы каждый класс, расширенный от uvm_object, имел конструктор с аргументом name. Это требование не имеет обратной совместимости и вызовет ошибку времени компиляции, если класс не содержит такого конструктора и вместо этого полагается на конструктор по умолчанию, предоставляемый SystemVerilog. К счастью, после предоставления конструктора обновленные классы остаются совместимыми с предыдущими версиями со старым (неправильным) поведением фабрики объектов UVM. Таким образом, пользователи могут перенести код вперед как можно скорее, не влияя на текущую работу, используя поведение библиотеки UVM по умолчанию.

Подходящие конструкторы могут быть автоматически добавлены к классам, в которых они отсутствуют, с помощью сценария add_uvm_object_new.pl. Следующая команда обновит все исходные файлы SystemVerilog, расположенные в текущем рабочем каталоге и любом подкаталоге:

Для получения дополнительной информации см. параметр командной строки --help.

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

выдаст следующие сообщения об ошибках:

Квест

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

Что произойдет в UVM 1.2?

В UVM 1.2 корректное поведение фабрики объектов UVM будет включено по умолчанию. Однако старое (неправильное) поведение останется доступным. Чтобы восстановить старое поведение, необходимо будет определить символ `UVM_OBJECT_OPTIONAL_CONSTRUCTOR при компиляции библиотеки UVM:

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

Что произойдет в UVM 1.3?

В UVM 1.3 фабрика объектов UVM будет иметь ТОЛЬКО исправленное поведение. Старое (неправильное) поведение больше не будет доступно.

Это сделано для того, чтобы поощрить пользователей к переносу своего кода и позволить поставщикам VIP в конечном итоге полагаться на правильное поведение фабрики объектов UVM.

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

Конструкторы могут дополнительно принимать список инициализаторов элементов. Это более эффективный способ инициализации членов класса, чем присвоение значений в теле конструктора. В следующем примере показан класс 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:

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