встроенное значение в интерфейсах модуля

24

Рассмотрим заголовочный файл:

class T
{
private:
  int const ID;

public:
  explicit T(int const ID_) noexcept : ID(ID_) {}

  int GetID() const noexcept { return ID; }
};

или, альтернативно:

class T
{
private:
  int const ID;

public:
  explicit T(int const ID_) noexcept;

  int GetID() const noexcept;
};

inline T::T(int const ID_) noexcept : ID(ID_) {}

inline int T::GetID() const noexcept { return ID; }

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

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

Мы разъяснили значение inlineинтерфейсов модулей: намерение состоит в том, чтобы тела функций, которые не были явно объявлены inline, не были частью ABI модуля, даже если эти тела функций появляются в интерфейсе модуля. Чтобы дать авторам модулей больший контроль над их ABI, функции-члены, определенные в телах классов в интерфейсах модулей, больше не являются неявными inline.

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

Если да, будет ли следующий интерфейс модуля эквивалентен приведенным выше заголовкам?

export module M;

export
class T
{
private:
  int const ID;

public:
  inline explicit T(int const ID_) noexcept : ID(ID_) {}

  inline int GetID() const noexcept { return ID; }
};

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

metalfox
источник

Ответы:

11

Означает ли это, что в мире модулей, чтобы компилятор мог оптимизировать вызовы функций, мы должны аннотировать их, inlineдаже если они определены в классе?

До некоторой степени.

Встраивание - это оптимизация «как будто», и встраивание может происходить даже между единицами перевода, если компилятор достаточно умен.

При этом встраивание проще всего при работе в пределах одного блока перевода. Таким образом, для облегчения встраивания, inline-объявленная функция должна иметь свое определение в любой единице перевода, где она используется. Это не означает, что компилятор, безусловно, встроит его (или, конечно, не inlineвстроит какую-либо неквалифицированную функцию), но это значительно облегчает процесс вставки, так как вставка происходит внутри TU, а не между ними.

Определения членов класса, определенные внутри класса в мире, предшествующем модулю, объявляются inlineнеявно. Почему? Потому что определение в классе. В пре-модульном мире определения классов, которые являются общими для TU, разделяются текстовым включением. Поэтому элементы, определенные в классе, будут определены в заголовке, совместно используемом этими TU. Таким образом, если несколько TU используют один и тот же класс, эти несколько TU делают это, включая определение класса и определение его членов, объявленных в заголовке.

То есть вы в любом случае включаете определение , так почему бы не сделать это inline?

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

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

Но вот в чем дело: это артефакт использования текстового включения в качестве средства доставки класса в несколько мест.

В модульном мире вы, вероятно, захотите определить каждую функцию-член внутри самого класса, как мы видим в других языках, таких как Java, C #, Python и тому подобное. Это сохраняет разумность локальности кода и предотвращает необходимость повторного ввода одной и той же сигнатуры функции, тем самым удовлетворяя потребности в DRY.

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

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

Николь Болас
источник
8

Это происходит от P1779 , только что принятого в Праге несколько дней назад. Из предложения:

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

В документе (между прочим) снято предложение:

Функция, определенная в определении класса, является встроенной функцией.

и добавил предложение:

В глобальном модуле функция, определенная в определении класса, неявно встроена ([class.mfct], [class.friend]).


Ваш пример с export module Mбудет модульным эквивалентом исходной программы. Обратите внимание, что компиляторы уже выполняют встроенные функции, которые не аннотированы inline, просто они дополнительно используют наличие inlineключевого слова в своей эвристике.

Барри
источник
Итак, функция в модуле без inlineключевого слова никогда не будет встроена компилятором, правильно?
metalfox
1
@metalfox Нет, я не верю, что это правильно.
Барри
1
Понимаю. Спасибо. Это похоже на то, как это было определено в файле cpp, что не обязательно означает, что он не будет встроен во время ссылки.
Metalfox