モジュールインターフェイスでのインラインの意味


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; }

モジュール前の世界では、これらのヘッダーは、ODR違反なしで複数のTUにテキストで含まれる場合があります。さらに、関連するメンバー関数は比較的小さいので、コンパイラーはそれらの関数を「インライン化」する(使用時に関数呼び出しを回避する)か、一部のインスタンスを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は、バイナリモジュールアーティファクトにそれらの関数の定義を含める必要があります。つまり、このような関数定義でコードを1行でも変更する場合は常に、それに依存するすべてのモジュールとともに、モジュールを再帰的にビルドする必要があります。

暗黙のinlineモジュールを削除すると、定義をクラスの外に移動しなくても、テキストの包含日と同じ権限をユーザーに与えることができます。モジュールの一部である関数定義とそうでない関数定義を選択できます。


8

これは数日前にプラハで採用されたばかりのP1779によるものです。提案から:

このドキュメントでは、(名前付き)モジュールにアタッチされたクラス定義で定義された関数から暗黙的なインラインステータスを削除することを提案します。これにより、クラスは冗長な宣言を避け、インラインでまたはなしで関数を宣言する際にモジュール作成者に提供される柔軟性を維持できるというメリットがあります。さらに、注入されたクラステンプレートのフレンド(これは一般的にクラス定義の外では定義できません)をインラインでなくすることができます。また、NBコメントUS90も解決します。

論文は(とりわけ)文を削除しました:

クラス定義内で定義された関数は、インライン関数です。

そして文を追加しました:

グローバルモジュールでは、クラス定義内で定義された関数は暗黙的にインライン化されます([class.mfct]、[class.friend])。


を使用した例export module Mは、初期プログラムと同等のモジュールです。コンパイラーは、注釈が付けられていないインライン関数をすでに実行していることに注意してください。これは、ヒューリスティックスでキーワードinlineの存在をさらに使用するだけですinline


したがって、inlineキーワードのないモジュール内の関数は、コンパイラーによってインライン化されることはありませんよね?
metalfox

1
@metalfoxいいえ、私はそれが正しいとは思わない。
バリー

1
そうですか。ありがとう。それはcppファイルで定義されたようなものですが、必ずしもリンク時にインライン化されないわけではありません。
metalfox
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.