web-dev-qa-db-ja.com

インライン変数はどのように機能しますか?

2016 Oulu ISO C++標準会議で、 Inline Variables という提案が標準委員会によってC++ 17に投票されました。

素人の言葉では、インライン変数とは何ですか、それらはどのように機能し、何のために有用ですか?インライン変数はどのように宣言、定義、使用する必要がありますか?

99
jotik

提案の最初の文:

_inline指定子は、関数に加えて変数に適用できます。

関数に適用されるinlineの「保証された効果」は、複数の翻訳単位で、外部リンケージを使用して、関数を同一に定義できるようにすることです。実際には、ヘッダーで関数を定義することを意味し、複数の翻訳単位に含めることができます。この提案は、この可能性を変数に拡張します。

したがって、実際には(現在受け入れられている)提案では、inlineキーワードを使用して、外部リンケージconst名前空間スコープ変数、またはstaticクラスデータメンバーをヘッダーで定義できます。そのヘッダーが複数の翻訳単位に含まれている場合に生じる複数の定義がリンカーで問題ないように、リンカーはoneを選択します。

C++ 14までは、クラステンプレートでstatic変数をサポートするためにこのための内部機構がありましたが、その機構を使用する便利な方法はありませんでした。次のようなトリックに頼らなければなりませんでした

template< class Dummy >
struct Kath_
{
    static std::string const hi;
};

template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";

using Kath = Kath_<void>;    // Allows you to write `Kath::hi`.

C++ 17以降では、

struct Kath
{
    static std::string const hi;
};

inline std::string const Kath::hi = "Zzzzz...";    // Simpler!

…ヘッダーファイル内。

提案には文言が含まれています

インライン静的データメンバーは、クラス定義で定義でき、ブレースまたはイコライザーの初期化子を指定できます。メンバーがconstexpr指定子で宣言されている場合、初期化子なしで名前空間スコープで再宣言できます(この使用法は非推奨です。D.Xを参照してください)。他の静的データメンバの宣言では、ブレースまたはイコールイニシャライザを指定してはなりません

…上記をさらに単純化して、

struct Kath
{
    static inline std::string const hi = "Zzzzz...";    // Simplest!
};

…T.Cがこの回答の コメント で述べているように。

また、​constexpr指定子は、静的データメンバーのinlineを関数としても意味します。


ノート:
¹関数の場合、inlineには最適化に関するヒント効果もあります。コンパイラは、この関数の呼び出しを関数のマシンコードの直接置換に置き換えることを選択する必要があります。このヒントは無視できます。

103

インライン変数は、インライン関数に非常によく似ています。変数が複数のコンパイル単位で見られる場合でも、変数のインスタンスが1つだけ存在することをリンカーに通知します。リンカは、これ以上コピーが作成されないようにする必要があります。

インライン変数を使用して、ヘッダーのみのライブラリでグローバルを定義できます。 C++ 17以前は、回避策(インライン関数またはテンプレートハック)を使用する必要がありました。

たとえば、1つの回避策は、インライン関数でMeyerのシングルトンを使用することです。

inline T& instance()
{
  static T global;
  return global;
}

このアプローチには、主にパフォーマンスの面でいくつかの欠点があります。このオーバーヘッドはテンプレートソリューションによって回避できますが、簡単に間違ってしまう可能性があります。

インライン変数を使用すると、直接宣言できます(複数定義リンカーエラーを取得することなく)。

inline T global;

ヘッダーのみのライブラリとは別に、インライン変数が役立つ場合があります。 Nir Friedmanは、CppConでの講演でこのトピックを取り上げています。 C++開発者がグローバル(およびリンカー)について知っておくべきこと 。インライン変数と回避策に関する部分 18m9sで始まる

要するに、コンパイル単位間で共有されるグローバル変数を宣言する必要がある場合、ヘッダーファイルでインライン変数として宣言するのは簡単で、C++ 17以前の回避策の問題を回避できます。

(例えば、明示的に遅延初期化を行いたい場合、Meyerのシングルトンのユースケースはまだあります。)

11
Philipp Claßen

最小限の実行可能な例

この素晴らしいC++ 17機能により、次のことが可能になります。

  • 各定数に1つのメモリアドレスのみを使用すると便利です
  • constexprとして保存します: constexpr externの宣言方法
  • 1つのヘッダーから1行で実行します

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

コンパイルして実行:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHubアップストリーム

参照: インライン変数はどのように機能しますか?

インライン変数のC++標準

C++標準では、アドレスが同じであることを保証しています。 C++ 17 N4659標準ドラフト 10.1.6 "インライン指定子":

6外部リンケージを持つインライン関数または変数は、すべての変換単位で同じアドレスを持っているものとします。

cppreference https://en.cppreference.com/w/cpp/language/inline は、staticが指定されていない場合、外部リンケージがあることを説明します。

GCCインライン変数の実装

以下を使用して、実装方法を確認できます。

nm main.o notmain.o

を含む:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

そしてman nmuについて述べています:

"u"シンボルは一意のグローバルシンボルです。これは、ELFシンボルバインディングの標準セットに対するGNU拡張です。このようなシンボルの場合、動的リンカーは、プロセス全体で、使用中のこの名前とタイプのシンボルが1つだけであることを確認します。

そのため、専用のELF拡張機能があることがわかります。

Pre-C++ 17:extern const

C++ 17より前、およびCでは、extern constを使用して非常によく似た効果を実現できます。これにより、単一のメモリロケーションが使用されます。

inlineの欠点は次のとおりです。

  • この手法で変数constexprを作成することはできません。inlineのみがそれを許可します。 constexpr externの宣言方法
  • ヘッダーとcppファイルで変数を個別に宣言および定義する必要があるため、エレガントではありません

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHubアップストリーム

C++ 17以前のヘッダーのみの代替

これらはexternソリューションほど優れていませんが、機能し、単一のメモリロケーションのみを占有します。

constexprconstexpr およびinlineを暗黙指定するため、inline関数 すべての翻訳単位に定義を表示する(強制する)

constexpr int shared_inline_constexpr() { return 42; }

そして、まともなコンパイラーが呼び出しをインライン化することは間違いないでしょう。

次のようにconstまたはconstexpr静的整数変数を使用することもできます。

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

しかし、アドレスを取得するなどのことはできません。さもないと、odrが使用されるようになります。 https://en.cppreference.com/w/cpp/language/static "Constant staticメンバー」および constexpr静的データメンバーの定義

C

Cでは、状況はC++ pre C++ 17と同じです。次の場所に例をアップロードしました。 Cで「静的」とはどういう意味ですか?

唯一の違いは、C++では、constがグローバルのstaticを意味するが、Cではそうではないということです。 C++の `static const`対` const`

完全にインライン化する方法はありますか?

TODO:メモリをまったく使用せずに変数を完全にインライン化する方法はありますか?

プリプロセッサの動作とよく似ています。

これには何らかの方法が必要です。

  • 変数のアドレスが取得されたかどうかを禁止または検出する
  • その情報をELFオブジェクトファイルに追加し、LTOに最適化させます

関連する:

Ubuntu 18.10、GCC 8.2.0でテスト済み。