встроенное значение в интерфейсах модуля - PullRequest
24 голосов
/ 18 февраля 2020

Рассмотрим файл заголовка:

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

Ответы [ 2 ]

11 голосов
/ 18 февраля 2020

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

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

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

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

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

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

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

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

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

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

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

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

8 голосов
/ 18 февраля 2020

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

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

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

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

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

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

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

...