- cpp17[meta cpp]
外部リンケージを持つ変数に対しインラインinline
を指定することで、複数の翻訳単位で同じ変数を定義できるようになり、変数の実体はただ一つとすることができる。
C++14までは関数のみインライン指定ができたが、C++17では関数、変数ともにインライン指定が可能になった。
これによりヘッダのみで変数の定義を行うことができるようになり、従来のようにヘッダで変数を宣言しソースで変数の実体を定義する必要がなくなった。
// C++17以降 -----
// ヘッダ
struct X {
// ソースで変数fooを定義する必要がない
static inline int foo;
};
// C++14以前 -----
// ヘッダ
struct X {
// ヘッダでは変数の宣言のみを行い
static int foo;
};
// ソース
// 変数fooを定義する
int X::foo;
inline
は関数または変数の宣言、定義に対して指定できる。外部リンケージを持つ関数、変数がどこか一つの翻訳単位でinline
指定された場合、同名の変数が宣言されている全ての翻訳単位でinline
指定を行う必要がある。inline
指定された関数、変数は全ての翻訳単位で同じアドレスに配置される。
変数定義のあとに後付けで初めてのinline
指定を行うのは不適格である。
int a = 100;
// 不適格につきコンパイルエラー
extern inline int a;
inline
指定した同名の変数は全て同じ定義にすべきである。翻訳単位によって異なる型、初期値で変数を定義すると、プログラムは正常に動作しない可能性がある (不適格(例外付き診断不要))。
// b.cpp
inline int hoge = 10;
// a.cpp
inline int hoge = 100;
int main()
{
// 不適格(診断不要)
// コンパイルエラーにならないかもしれないが、正常に動作しない可能性がある
return hoge;
}
constexpr
を指定した関数または静的メンバ変数は、暗黙のうちにinline
を指定したことになる。逆に言えばグローバルなconstexpr
変数の場合だけ、明示的にinline
指定する必要がある。
inline
の文法上の変化としては、C++14ではfunction-specifier
つまり関数専用のキーワードに属していたが、C++17では関数、変数どちらにも指定できるdecl-specifier
に移動している。
//関数の場合
function-definition:
attribute-specifier-seq opt decl-specifier-seq opt declarator virt-specifier-seq opt function-body
//変数の場合
simple-declaration:
decl-specifier-seq opt init-declarator-list opt ;
attribute-specifier-seq decl-specifier-seq opt init-declarator-list ;
member-declaration:
attribute-specifier-seq opt decl-specifier-seq opt member-declarator-list opt ;
decl-specifier:
storage-class-specifier
type-specifier
function-specifier
friend
typedef
constexpr
inline //C++17で追加
function-specifier:
inline //C++17で削除
virtual
explicit
//inline_variable.hpp
#include <iostream>
namespace N {
// 外部リンケージ & 非インライン
// →ODR違反により不適格
/*
int var = 0;
int func() {
return 0;
}
*/
// 外部リンケージ & インライン(inline指定)
// →全翻訳単位でアドレスは同一
inline int inline_var = 10;
inline int inline_func() {
return 20;
}
// 内部リンケージ(static指定) & 非インライン
// →翻訳単位毎に異なるアドレス
static int static_var = 30;
static int static_func() {
return 40;
}
// 内部リンケージ(static指定) & インライン(inline指定)
// →外部リンケージではないのでインライン指定はアドレスに影響しない。
// static のみ指定したときと同様に、翻訳単位毎に異なるアドレスになる。
static inline int static_inline_var = 50;
static inline int static_inline_func() {
return 60;
}
// 外部リンケージ & 非インライン(constexpr変数は暗黙にinlineにはならない)
constexpr int constexpr_var = 70;
// 外部リンケージ & インライン(constexpr関数は暗黙にinlineとなる)
constexpr int constexpr_func() {
return 80;
}
}
struct A {
// inline指定(全翻訳単位でアドレスは同一)
static inline int inline_var = 100;
static inline int inline_func() {
return 200;
}
// staticメンバ変数かつconstexprなので
// 暗黙のうちにinlineが指定される
static constexpr int constexpr_var = 300;
// 関数かつconstexprなので
// 暗黙のうちにinlineが指定される
static constexpr int constexpr_func() {
return 400;
}
};
void func();
//inline_variable1.cpp
#include <iostream>
#include "inline_variable.hpp"
int main()
{
std::cout << __func__ << std::endl
<< " N::inline_var :" << &N::inline_var << std::endl
<< " N::inline_func :" << reinterpret_cast<void *>(N::inline_func) << std::endl
<< " N::static_var :" << &N::static_var << std::endl
<< " N::static_func :" << reinterpret_cast<void *>(N::static_func) << std::endl
<< " N::static_inline_var :" << &N::static_inline_var << std::endl
<< " N::static_inline_func:" << reinterpret_cast<void *>(N::static_inline_func) << std::endl
<< " N::constexpr_var :" << &N::constexpr_var << std::endl
<< " N::constexpr_func :" << reinterpret_cast<void *>(N::constexpr_func) << std::endl
<< std::endl
<< " A::inline_var :" << &A::inline_var << std::endl
<< " A::inline_func :" << reinterpret_cast<void *>(A::inline_func) << std::endl
<< " A::constexpr_var :" << &A::constexpr_var << std::endl
<< " A::constexpr_func :" << reinterpret_cast<void *>(A::constexpr_func) << std::endl
<< std::endl;
func();
return 0;
}
//inline_variable2.cpp
#include <iostream>
#include "inline_variable.hpp"
void func()
{
std::cout << __func__ << std::endl
<< " N::inline_var :" << &N::inline_var << std::endl
<< " N::inline_func :" << reinterpret_cast<void *>(N::inline_func) << std::endl
<< " N::static_var :" << &N::static_var << std::endl
<< " N::static_func :" << reinterpret_cast<void *>(N::static_func) << std::endl
<< " N::static_inline_var :" << &N::static_inline_var << std::endl
<< " N::static_inline_func:" << reinterpret_cast<void *>(N::static_inline_func) << std::endl
<< " N::constexpr_var :" << &N::constexpr_var << std::endl
<< " N::constexpr_func :" << reinterpret_cast<void *>(N::constexpr_func) << std::endl
<< std::endl
<< " A::inline_var :" << &A::inline_var << std::endl
<< " A::inline_func :" << reinterpret_cast<void *>(A::inline_func) << std::endl
<< " A::constexpr_var :" << &A::constexpr_var << std::endl
<< " A::constexpr_func :" << reinterpret_cast<void *>(A::constexpr_func) << std::endl
<< std::endl;
}
clang++ 14.0.0 (Fedora 14.0.0-1.fc36) にて amd64 向けにコンパイル、実行した場合。
main
N::inline_var :0x404054
N::inline_func :0x401550
N::static_var :0x404058
N::static_func :0x401530
N::static_inline_var :0x40405c
N::static_inline_func:0x401540
N::constexpr_var :0x402144
N::constexpr_func :0x401560
A::inline_var :0x404060
A::inline_func :0x401570
A::constexpr_var :0x402148
A::constexpr_func :0x401580
func
N::inline_var :0x404054
N::inline_func :0x401550
N::static_var :0x404064
N::static_func :0x401870
N::static_inline_var :0x404068
N::static_inline_func:0x401880
N::constexpr_var :0x402154
N::constexpr_func :0x401560
A::inline_var :0x404060
A::inline_func :0x401570
A::constexpr_var :0x402148
A::constexpr_func :0x401580
表示されるアドレスは環境によって異なる可能性がある。
- N4147 - Inline variables, or encapsulated expressions?, 2014-09-25
- N4424 - Inline Variables, 2015-04-07
- P0386R0 - Inline Variables, 2016-05-30
- P0386R2 - Inline Variables, 2016-06-24
- P0607R0 - Inline Variables for the Standard Library, 2017-02-27
- C++1z インライン変数 - Faith and Brave - C++で遊ぼう