2016 Oulu ISO C++標準会議で、 Inline Variables という提案が標準委員会によってC++ 17に投票されました。
素人の言葉では、インライン変数とは何ですか、それらはどのように機能し、何のために有用ですか?インライン変数はどのように宣言、定義、使用する必要がありますか?
提案の最初の文:
」_
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
には最適化に関するヒント効果もあります。コンパイラは、この関数の呼び出しを関数のマシンコードの直接置換に置き換えることを選択する必要があります。このヒントは無視できます。
インライン変数は、インライン関数に非常によく似ています。変数が複数のコンパイル単位で見られる場合でも、変数のインスタンスが1つだけ存在することをリンカーに通知します。リンカは、これ以上コピーが作成されないようにする必要があります。
インライン変数を使用して、ヘッダーのみのライブラリでグローバルを定義できます。 C++ 17以前は、回避策(インライン関数またはテンプレートハック)を使用する必要がありました。
たとえば、1つの回避策は、インライン関数でMeyerのシングルトンを使用することです。
inline T& instance()
{
static T global;
return global;
}
このアプローチには、主にパフォーマンスの面でいくつかの欠点があります。このオーバーヘッドはテンプレートソリューションによって回避できますが、簡単に間違ってしまう可能性があります。
インライン変数を使用すると、直接宣言できます(複数定義リンカーエラーを取得することなく)。
inline T global;
ヘッダーのみのライブラリとは別に、インライン変数が役立つ場合があります。 Nir Friedmanは、CppConでの講演でこのトピックを取り上げています。 C++開発者がグローバル(およびリンカー)について知っておくべきこと 。インライン変数と回避策に関する部分 18m9sで始まる 。
要するに、コンパイル単位間で共有されるグローバル変数を宣言する必要がある場合、ヘッダーファイルでインライン変数として宣言するのは簡単で、C++ 17以前の回避策の問題を回避できます。
(例えば、明示的に遅延初期化を行いたい場合、Meyerのシングルトンのユースケースはまだあります。)
最小限の実行可能な例
この素晴らしいC++ 17機能により、次のことが可能になります。
constexpr
として保存します: constexpr externの宣言方法main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_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 ¬main_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
インライン変数の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 nm
はu
について述べています:
"u"シンボルは一意のグローバルシンボルです。これは、ELFシンボルバインディングの標準セットに対するGNU拡張です。このようなシンボルの場合、動的リンカーは、プロセス全体で、使用中のこの名前とタイプのシンボルが1つだけであることを確認します。
そのため、専用のELF拡張機能があることがわかります。
Pre-C++ 17:extern const
C++ 17より前、およびCでは、extern const
を使用して非常によく似た効果を実現できます。これにより、単一のメモリロケーションが使用されます。
inline
の欠点は次のとおりです。
constexpr
を作成することはできません。inline
のみがそれを許可します。 constexpr externの宣言方法main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.cpp
#include "notmain.hpp"
const int notmain_i = 42;
const int* notmain_func() {
return ¬main_i;
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
extern const int notmain_i;
const int* notmain_func();
#endif
C++ 17以前のヘッダーのみの代替
これらはextern
ソリューションほど優れていませんが、機能し、単一のメモリロケーションのみを占有します。
constexpr
はconstexpr
および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:メモリをまったく使用せずに変数を完全にインライン化する方法はありますか?
プリプロセッサの動作とよく似ています。
これには何らかの方法が必要です。
関連する:
Ubuntu 18.10、GCC 8.2.0でテスト済み。