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


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так, коли це доречно, щоб мінімізувати майбутнє рефакторинг.

Відповіді:


11

Чи означає це, що у світі модулів, щоб компілятор міг оптимізувати виклики функцій, нам потрібно анотувати їх так, inlineнавіть якщо вони визначені в класі?

Певною мірою.

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

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

Визначення членів класу, визначені в класі, у світі перед модулем, декларуються inlineнеявно. Чому? Тому що визначення знаходиться в межах класу. У передмодульному світі визначення класів, які поділяються між TU, поділяються текстовим включенням. Члени, визначені в класі, отже, будуть визначені в заголовку, поділеному між цими TU. Отже, якщо кілька TU використовують один клас, ці кілька TU роблять це, включаючи визначення класу та визначення його членів, оголошених у заголовку.

Тобто ви все одно включаєте визначення , то чому б не зробити його inline?

Звичайно, це означає, що визначення функції тепер є частиною тексту класу. Якщо ви зміните визначення члена, оголошеного в заголовку, це змушує рекупіляцію кожного файлу, що включає цей заголовок, рекурсивно. Навіть якщо інтерфейс класу сам не змінюється, вам все одно потрібно зробити перекомпіляцію. Тому створення таких функцій неявно inlineце не змінює, тож ви також можете це зробити.

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

Але ось у чому річ: це артефакт використання текстового включення як засобу доставки класу до кількох місць.

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

Але якщо всі члени визначені в межах визначення класу, то за старими правилами всі ці члени були б inline. А щоб модуль мав можливість функціонувати inline, артефакт двійкового модуля повинен містити визначення цих функцій. Що означає, що щоразу, коли ви змінюєте навіть один рядок коду в такому визначенні функції, модуль повинен будуватися разом із кожним модулем залежно від нього рекурсивно.

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


8

Це походить від P1779 , щойно прийнятого в Празі кілька днів тому. З пропозиції:

У цій роботі пропонується видалити неявний вбудований статус з функцій, визначених у визначенні класу, приєднаному до (названого) модуля. Це дозволяє класам отримати перевагу від уникнення надмірних декларацій, зберігаючи гнучкість, запропоновану авторам модулів при оголошенні функцій з вбудованим або без введення. Більше того, він дозволяє ін'єктованим друзям шаблонів класів (які не можуть бути загально визначені за межами визначення класу) взагалі не вбудовані. Він також вирішує коментар NB9090 .

У статті (серед іншого) вилучено речення:

Функція, визначена у визначенні класу, є вбудованою функцією.

і додав речення:

У глобальному модулі функція, визначена у визначенні класу, неявно вбудована ([class.mfct], [class.friend]).


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


Отже, функція в модулі без inlineключового слова ніколи не буде накреслена компілятором, правда?
металфокс

1
@metalfox Ні, я не вірю, що це правильно.
Баррі

1
Розумію. Дякую. Це так, як це було визначено у файлі cpp, що не обов'язково означає, що він не буде розміщений під час посилання.
металфокс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.