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

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

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, когда это уместно, чтобы свести к минимуму рефакторинг в будущем.


person metalfox    schedule 18.02.2020    source источник


Ответы (2)


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

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

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

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

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

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

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

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

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

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

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

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

person Nicol Bolas    schedule 18.02.2020

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

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

Газета (среди прочего) удалила фразу:

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

и добавил фразу:

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


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

person Barry    schedule 18.02.2020
comment
Итак, функция в модуле без ключевого слова inline никогда не будет встроена компилятором, верно? - person metalfox; 18.02.2020
comment
Я понимаю. Спасибо. Это похоже на то, как это было определено в файле cpp, что не обязательно означает, что он не будет встроен во время компоновки. - person metalfox; 19.02.2020