What’s New In C++11 言語仕様¶
このノートでは C++11 で注目すべき言語仕様を学習する。すでに cpprefjp がそのへんをきれいに整理している。それを利用して、読みながら急所を記していくことにする。
タイピングの都合で訳語は cpprefjp のものと一部変更して記す。
一般的な機能¶
型推論 auto
¶
いちばんありがたいのが仕様転生したキーワード auto
だ。変数の宣言と定義を同時にする場合には、右辺の値の型を明示する代わりに auto
と書く。
テンプレートの型推論機能と同等のものと考えて良いようだ。
auto x = 5; // OK: x has type int
const auto* v = &x, u = 6; // OK: v has type const int*, u has type const int
static auto y = 0.0; // OK: y has type double
std::string line;
// ...
boost::tokenizer<escaped_list_separator<char>> tok(line);
for(auto first = tok.begin(), last = tok.end(); first != last; ++first){
// ...
}
従来の自動変数(スタックに積む変数)を意味するためのキーワードという意味は廃止された。
const
/volatile
,*
,&
,&&
と共に用いることも可能だ。テンプレート型引数や関数引数の型として用いることは許されない。
この他にも、C++11 から仕様化された trailing-return-type を含む関数宣言でも引数や戻り値など、有効な宣言である限り、この手の
auto
を用いることが許される。
式の型を取得 decltype
¶
decltype
は型を指定する必要のある場所で用いる。オペランドは式とする。演算結果がその式の返す値の型となる。コンパイル時に評価される。
宣言 decltype(e)
によって定義される型は次のように決まる:
e
が括弧なし id-expression または括弧なしクラスメンバーアクセスのときはdecltype(e)
はe
で与えられている実体の型とする。そのような実体が存在しないか、オーバーロード関数の集合を与えるときには、このコードは ill-formed であるとする。もしくは
e
が xvalue ならばdecltype(e)
はT&&
とする。ここでT
はe
の型とする。もしくは
e
が lvalue ならばdecltype(e)
はT&
とする。それ以外の場合には
decltype(e)
はe
の型そのものとする。decltype
は変数宣言の用途にはあまり向かない。式自体を二度書く必要がある。decltype
のオペランドは値には評価されない。例えばdecltype(2 + 3)
とあっても、コンパイラーはこの和を使わない。型だけを見る。
以下、標準の例コードからの引用だ。
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4 = x3; // type is const double&
テンプレートが絡むと話は複雑になるのだが、今はこれで十分だろう。
範囲 for
文¶
よそのプログラミング言語ではおなじみの機能が C++11 に追加された。cpprefjp のコードを引用する:
std::vector<int> v;
for(const auto& e : v){
std::cout << e << std::endl;
}
この記法が許されるコンテナーは生の配列か、begin()
, end()
および反復子を進める機能を提供するコンテナー型オブジェクトだと思って問題ない。
初期化子リスト¶
生の配列や POD 構造体のように、オブジェクトを { ... };
の構文で初期化できる機能だ。
std::complex<double> z{1, 2};
std::map<std::string, int> anim = {
{"bear", 4},
{"cassowary", 2},
{"tiger", 7}
};
パラメーターリストにクラステンプレート std::initializer_list
の引数を取るコンストラクターが提供されているクラスに対してそのような初期化が許される。
#include <initializer_list>
template <typename T>
class MyClass{
public:
MyClass(std::initializer_list<T>);
// ...
};
explicit
コンストラクターの例はどうも好かない。コンストラクターの他に、
operator new
呼び出し、return
文などでも用いることができる。初期化子リストに列挙した要素は、先頭から順番に評価される。
この機能の急所は、テンプレートやオーバーロードの仕様との絡み。いちいち確認するほうがいい。
一様初期化 brace-or-equal-initializer¶
コンストラクターの呼び出しを丸括弧だけではなく、上述の初期化子リストを引数に取るコンストラクターの呼び出しと同じ構文で書ける。
例えばデフォルトコンストラクターの呼び出しですら(厳密には細かい条件があるものの)こう書ける:
// いずれもデフォルトコンストラクターによる初期化を意味する。 // コピーコンストラクターもコピー代入演算子も呼び出されない。 MyClass x; MyClass y{}; MyClass z = {};
標準ではこのような初期化の記法を brace-or-equal-initializer と呼んでいる。特にその記法の構成要素である次のものを braced-init-list と呼ぶ:
初期化子リスト(のカンマ区切り)を挟む中括弧全体もしくは
中身のない中括弧の組
呼び出し時に列挙した要素は、先頭から順番に評価される。
この機能の急所もやはり初期化子リストと同様だ。
右辺値参照・ムーブセマンティクス¶
右辺値・左辺値の定義は問題ない。C++03 以前のものが通じる。
と思ったら、C++11 から次のような式の分類法が導入されたようだ:
glvalue は lvalue または xvalue のどちらかに分類される。「一般化された左辺値」くらいの意味。
lvalue とは関数またはオブジェクトを指す。
例:
ptr
をポインター型の式とすると*ptr
はptr
が指すオブジェクトまたは関数を参照する lvalue だ。例:戻り型がある lvalue 参照であるような関数の呼び出し結果は lvalue だ。
xvalue もまたあるオブジェクトを、通常はその寿命の終わりが近い (expiring) ようなものを参照する。xvalue は rvalue 参照を引き起こすような式の結果だ。
例:戻り型がある rvalue 参照であるような関数の呼び出し結果は xvalue だ。
rvalue とは xvalue であるか、一時オブジェクトまたは部分オブジェクトであるか、あるいはオブジェクトに関係していない値だ。
prvalue とは xvalue でない rvalue だ。
例:戻り型が参照でない関数の呼び出し結果は prvalue だ。
例:
12
とか7.3e5
とかtrue
のようなリテラル値もまた prvalue だ。
これは分類であるので、任意の C++ の式はlvalue, xvalue, rvalue のいずれかちょうど一つに当てはまると言っている。
〈右辺値参照は、右辺値を束縛する参照である〉
C++03 以前までの参照を 左辺値参照 と呼ぶようになったらしい。
T&
だろうがconst T&
だろうが左辺値参照だ。C++11 から rvalue 専用の処理を記述する機能が提供される。
C++11 で追加になった
&&
で宣言される参照型を 右辺値参照 と呼ぶ。
〈C++11 以降では、代入式 2 の右辺の型は右辺値参照
vector<int>&&
になり、左辺値参照とは区別して処理される。また、こうした処理のことを「ムーブ」と呼ぶ〉std::vector<int> v, vv; v = vv; // 代入式 1 v = std::vector<int>(100, 0); // 代入式 2
この式自体は
std::vector<...>::operator=(rhs)
の呼び出しとコンストラクターが組み合わさったものだ。本文はこのrhs
が C++11 では rvalue 参照&&
であると主張しているようだ。
右辺値参照とは、ムーブされたオブジェクトを示すものだ。
rvalue を
const
なし lvalue 参照することは許されない。lvalue を rvalue 参照することは許されない。
〈右辺値参照で宣言された変数は右辺値ではなく、左辺値である〉
int&& x = 0;
に対してx
は lvalue であるということを言っている。したがって例えばこの後でint&& y = x;
などとはできない。
次に std::move()
について学習する。この関数の挙動を説明するのに右辺値参照の概念が要るのだ。
〈ムーブされた変数は右辺値となり、それ以降使える保証はなくなる〉。この Hello world は実際にコードをコンパイルして実行するといい。コメント通りの挙動を確認できる。要するに、ムーブされた変数にもし名前が付いていたならば、それは「抜け殻」になっているので、もはや参照してはならない。
とにかく
std::move()
すると、その引数は xvalue 化してしかも寿命が尽きると考えればいい。実効性のあるムーブはムーブ代入またはムーブ構築で起こると考える。
この関数自体は単なるキャストしかしていないので、実行時オーバーヘッドなどはない。それどころか実行コードを何ら生成しないまである。
所有権の移動の実例は自然でわかりやすい。
C++11 から、コンストラクターおよび代入演算子に(従来型のコピーとは別の)ムーブ版が考えられるようになった。
ムーブコンストラクターとムーブ代入演算子のシグニチャーに注目。
const
なし&&
付き。実装例は想像しているとおり。cpprefjp の
large_class
のデモコードでは抜け殻のメンバー変数をクリアしていることに注意。
〈標準ライブラリで提供されるクラスのほとんどは、このようなムーブコンストラクタを用意している〉代入演算子も同様。とにかく lvalue に対して
std::move()
を介せばいい。ムーブコンストラクター・ムーブ代入演算子は、それらのコピー版もそうであるように、コンパイラーが自動生成するケースがある。その条件は本文にある通りだが、ここではコピー演算、ムーブ演算の意味が明白でない(演算子ということか?)
以下のトピックは以上を完全に習得してから学習すること。
転送参照
テンプレートや
auto
に&&
をつける場合は上記のときと意味が異なる。右辺値参照しか表さないとは限らない。 cpprefjp の説明によると、次の関数テンプレートf
において実引数がlvalue ならば
x
は lvalue 参照となる。rvalue ならば
x
は rvalue 参照となる。
template <typename T> void f(T&& x) {}
完全転送
引数を転送参照で宣言した場合、安易にムーブできない。引数は rvalue 参照の場合と lvalue 参照の場合両方があり得る。
例えば上述の関数テンプレート
f()
の定義において、別の関数g()
にx
を渡したいとする。x
が rvalue の場合にはムーブするのが自然なのでそうしたい。しかしx
が lvalue の場合にはそのまま関数g()
に引き渡したい。こういう引数の引き渡し方を 完全転送 という。関数テンプレート
std::forward()
は完全転送を実現する。つまり lvalue 参照の場合はムーブせず、rvalue 参照の場合はムーブして渡す。この関数テンプレートはstd::move()
と違ってテンプレートを明示しなければ呼び出せないのが急所だ。
あとはこの機能が必要になった背景・経緯を読んでおくといい。やはり
std::auto_ptr
はインターフェイスに問題があった。しかしそのおかげでここで観てきたような諸概念が標準に導入されたのだから良かった。
ちなみに std::auto_ptr
は C++11 では廃止予定機能。
ラムダ式¶
Python の lambda 式と期待される役割は同じ。ありがたいことに lambda といちいちスペルアウトせずにそれを定義できる。
概要を理解したら capture と呼ばれる機能を理解する。
copy と reference とで分類される。
すべての capture notation を習得する。
this
を capture した場合、lambda 式が定義する関数オブジェクトは当該クラスのfriend
扱い。〈ラムダ式がデフォルト引数に現れる場合、いかなるキャプチャもしてはならない〉
文法を習得する。
"[" キャプチャリスト "]" "(" パラメータリスト ")" "mutable" 例外仕様 属性 "->" 戻り値の型 "{" 関数の本体 "}"
このうちのいくつかは省略できる。
〈ラムダ式は、その場に関数オブジェクトのクラスを定義し、その一時オブジェクトを生成する。関数オブジェクトのクラスを「クロージャ型 (closure type)」、その一時オブジェクトを「クロージャオブジェクト (closure object)」という〉
キーワード
mutable
copy capture した変数を変更するためにこの記述をパラメーターリストの直後に要する。
〈戻り値の型を省略した場合、その戻り値型は、関数本体の
return
文から推論される〉ので、次のようなコードは許されない:複数の
return
パスが存在するときに、それらの戻り値の推論型が一致しない場合リテラルの
std::initializer_list
による初期化リスト
〈キャプチャを含まないラムダ式によって生成されたクロージャオブジェクトは、同じパラメータ型と戻り値型のシグニチャを持つ関数ポインタに変換できる〉
〈標準 C++ のアルゴリズムライブラリには、関数を引数とするものが多くある。それらは各問題を解くために非常に有用ではあったが、アルゴリズムを使用するたびに関数オブジェクトを定義することは非常に冗長で使いにくかった。アルゴリズムをより使いやすくするための言語サポートとして、ラムダ式が導入されることとなった〉
キーワード noexcept
¶
noexcept
は C++ という言語の基本にある例外送出に関係する重要な概念なので優先度は高い。
新キーワード
noexcept
は、関数宣言において、例外を送出するか否かを明示するキーワードだ。必ずしも書かなくともよい。void f() noexcept; void g() noexcept(true); void h() noexcept(false);
関数が例外を送出するか否かをコンパイル時に判定する演算子
関数呼び出しを
noexcept()
で括る。すると例外を送出する可能性のあるものはfalse
に展開される。その呼び出し自体は実行時に実行されない。
指定が書いてなければ関数のほとんどは
noexcept(false)
としてコンパイラーが関数を扱う。その例外がデストラクターとoperator delete
であり、コンパイラーはnoexcept(true)
とみなす。このへんの理由は C++03 時代の書籍で理解できるだろう。noexcept
の利点の一つにパフォーマンス向上があることに注意。その理由は例外送出時のスタック巻き戻しに必要なメモリ領域を確保するのを省くようになるからだと。もう一つの理由はもちろん no-throw 保証。関数宣言のときに用いる
throw
による例外指定は C++11 で廃止予定機能となった。注意。どんな例外を送出するのかということより、例外送出の有無が本質的だったようだ。
定数式 constexpr
¶
constexpr
は誤解なきよう習得すること。それと std::numeric_limits
の従来の実装に関する〈抽象化された機能を使うより、抽象化されていない機能の方がよい、というのは、改善すべき事態だった〉という cpprefjp の一文が印象的だ。
constexpr
の目的は値、関数、リテラルを翻訳時に処理するものを宣言することにあるようだ。したがって constexpr
を宣言できるのは次のものに限られる:
変数定義
関数宣言、テンプレート関数宣言
リテラル型の静的メンバー変数の宣言
あとは細かいルールの列挙になるので、仕様書を見たほうがいい。
関数宣言やテンプレート関数宣言のどれか一つでも
constexpr
であるならばその宣言のすべてがconstexpr
でなければならない。constexpr
関数およびconstexpr
コンストラクターは暗黙的にinline
となる。constexpr
関数の定義には次の制約をすべて満たすものとする:仮想関数であってはならない。
戻り値はリテラル型でなければならない。
引数の一つ一つがリテラル型でなければならない。
関数本体が
= delete
または= default
である。そうでなければ、次に挙げるもの以外を含まない複合文でなければならない:空の文
static_assert()
typedef
宣言、クラスや列挙型を定義しないような別名宣言using
宣言using
指令厳密に一つの
return
文
constexpr
コンストラクターの定義では、引数型のそれぞれはリテラル型であるものとする。さらにその関数本体は= delete
または= default
であるか、上述の制約によく似た制約をすべて満たすものとする。
constexpr
関数に対する呼び出しは、あらゆる点において、それと等価な非constexpr
関数に対する呼び出しと同じ結果を生じる。コンストラクターを除く非静的メンバー関数に対する
constexpr
指示子は、そのメンバー関数がconst
であることを宣言している。なお、メンバー関数に
const
を書いたとしてもコンパイラーはそれを無視する。constexpr
メンバー関数を有するクラスはリテラル型であるものとする。
オブジェクト宣言での
constexpr
指示子はそのオブジェクトがconst
であることを宣言している。そのようなオブジェクトはリテラル型かつ初期化を伴っているものとする。あとはテンプレートと絡んだ仕様があるが省略。
読者ノート
仕様書の定義を読むとすごく時間がかかる。何か直観に訴えるノートにしたい。
空ポインター nullptr
¶
nullptr
の特性はまともな C++03 プログラマーの直観と合致する。しかし 0
と書くほうが楽なのだが。
nullptr
は唯一のポインターリテラルであり、予約語の形で提供されている。型
std::nullptr_t
の純右辺値 (prvalue) ということになっている。つまりtrue
,false
と同じ立場にいる値だ。
インライン名前空間 inline namespace
¶
inline namespace
はどういう立場になったときに使うのか。
C++11 で
inline namespace
という構文が追加された。この機能の急所は
using namespace
文で何がスコープに持ち込まれるのかを見分けることと、引数依存の名前探索ルールを理解することだろう。例えば cpprefjp の例だが:namespace my_namespace { inline namespace features { void f(){ ... } } void g(){ ... } }
using namespace my_namespace::features
の一文だけでf
とg
の両方を宣言するスコープに持ち込める。このリファレンスの記事だけでは名前なし
inline namespace
の概要がわからない?
ユーザー定義リテラル¶
ユーザー定義リテラルはシグニチャーが風変わりであることだけを頭に入れて後回し。
クラス¶
キーワード default
/delete
¶
キーワード default
, delete
を関数宣言に付与する使い方。cpprefjp の当該記事の要約は理想的。そしてこれらの用法は要習得。便利なので。
一定の種類のクラスを定義するときに、ユーザーがそれを明示的に定義しなければ一定の特別なメンバー関数をコンパイラーが自動生成する。C++11 では、その「暗黙の定義を使う」という宣言を明示的にコードに表すことができる。それが = default
記法だ。
特別メンバー関数 とは次のメンバー関数の総称だ:
デフォルトコンストラクター
コピーコンストラクター
コピー代入演算子
ムーブコンストラクター
ムーブ代入演算子
デストラクター
反対に、コンパイラーに自動生成を禁止するように指示するのが = delete
記法だ。例えば Singleton クラスを定義するときにこれが有用であることは想像できる。 C++03
では private
メンバーとして宣言だけしておくという技法を用いていた。
= delete
はフリー関数にも適用可能。これは特定のオーバーロードを禁じるという用法になる。= default
は関数定義にも書かれる。他方= delete
は宣言時に限定される。
委譲コンストラクター¶
委譲コンストラクターも要習得。コンストラクターが複数あるクラスを書いたことがある人間ならばこの新機能の意義は肌感覚で理解できる。
非静的メンバーの初期化¶
非静的メンバ変数の初期化。コンストラクターの定義によってはこの代入が実際に行われないことがあることに注意。
継承コンストラクター¶
派生クラスのコンストラクターの引数リストは基底クラスのそれと同じになりがちだということで生まれた新機能。using BaseClassName::BaseClassName;
を派生クラスに一筆加える。オプショナルに、その上派生クラス独自の定義をしてもよい。
キーワード final
/override
¶
キーワード final
は用途が二つある。クラス丸ごとと仮想メンバー関数ごと。
キーワード override
の存在がうれしいかと言われると微妙だ。オーバーライドするつもりが間違えてオーバーロードになってしまうミスを防ぐくらいにしか役に立たない?
これらのキーワードはコンテキスト依存キーワードという特別なキーワードとのこと。
この記事では角括弧を二重に使う新文法が見える。これは属性構文というもので後述する。
明示的な型変換演算子のオーバーロード¶
C++03 以前に explicit
コンストラクターというのがある。この考え方を C++11 では型変換演算子 operator T()
にも適用できる。
これはいい仕様追加だ。
cpprefjp のサンプルコードが実のところ少し高級かもしれない。bool
型でないほうがわかりやすい?
friend
宣言の要件の緩和¶
私は friend
機能を一切採用しないのでスキップ。cpprefjp の記事も短いし。
メンバ関数の左辺値/右辺値修飾¶
class MyClass
{
void f() &;
voif g() &&;
};
メンバー関数が左辺値・右辺値である場合にしか呼び出されないことを保証する記法が仕様に含まれた。メンバー関数の宣言の末尾に &
または &&
を付加することでそのような制限を加えることを意味する。 〈これを使用することで、「一時オブジェクトな *this
に対して特定のメンバ関数を呼び出せてはならない制約」、「*this
が左辺値もしくは右辺値である場合の効率的な実装を使い分ける」といった設計ができるようになる〉。
サンプルコード。〈参照を返すと一時オブジェクトの寿命が尽きてしまうため、ムーブで返す〉とある。これだと何か悪いことを避けているように読めるが違う。ここで示しているのは、むしろ寿命が尽きることを利用して std::move()
を使ってデータを流用してコピーを避けるという優れた技法だ。
クラス以外の型に関する機能¶
Scoped enum
¶
新文法
enum class
で列挙型を定義すると、暗黙の型変換が無効になり、列挙子が列挙型スコープを有する。「基底型」を宣言することができる。列挙子が少なければ
char
で十分とか?従来の
enum
にもこれが適用できる。
これにより列挙型の前方宣言が可能になった。宣言と定義の「ガワ」は一致していなければならない。
union
に対する規制緩和¶
union
のメンバー変数にユーザー定義型オブジェクトを置くことができる。共用体は全く使わないのでどうでもいい。
テンプレート¶
テンプレートの >
を空白を挟まずに書けるようになった¶
これは仕事で C++ をやった人間なら一発で理解できる。
std::map<int, std::vector<double> > positions;
みたいなものが次のように書けるようになったということ。
std::map<int, std::vector<double>> positions;
コンパイラーが後の方の宣言を operator>>
として扱って結果的に構文エラーになっていた。C++11 でこれがなくなる。
外部テンプレート extern template
¶
クラステンプレート C
または関数テンプレート F
が存在して、それを実体化するとサイズが巨大になるようなものであると仮定する。さらに、複数の cpp で C
または F
を参照するとし、cpp の一つで特定の実体化がされているとする。
このとき、別の cpp で次のように宣言すると、その cpp では実体化をしなくなる。別の cpp の実体化を参照するのだろう。
extern template class C<T1>;
extern template F<T2>(the_parameter_list);
この機能をどうやって検証しよう?
別名テンプレート¶
テンプレートを含む型に別名をつける方法ができた。例えば次のようにする。
template <class T>
using Vec = std::vector<T>;
キーワード using
の強化の話をまだ学習していないことに注意。別名定義はテンプレートが関係していなくてもいい。例えば次のような宣言も正しい:
using VecInt = std::vector<int>;
可変引数テンプレート¶
template <typename... Args>
というような文法になる。この ...
はこのままタイプする。ここで Args
のことを parameter pack という。
定義中で paramter pack を他の要素に転送するときは Args...
というような記法を使う。
問題はたぶん parameter pack の要素を一つずつ参照・処理する場合だろう。
サンプルコードを g++ (GCC) 10.2.0 でコンパイルしたら時間がかかって驚く。
あと paramter pack の拡張機能というのがある。要素それぞれに同じ処理を適用する式を書くこともできる。これは使うかもしれない。
ローカル型と無名型をテンプレート実引数として使用することができる¶
〈ローカルで定義した型と、名前のない型を、テンプレート引数として渡すことが許可された〉そうだ。
関数スコープで古典的な enum
を定義することを考える。これは無名列挙型として定義する必要がある。そうなると、そのスコープから関数テンプレートなどの実引数として列挙子を与えるコードがコンパイルエラーを引き起こしていた。関数スコープでの定義を諦めることで回避していたが、その必要がなくなる。
SFINAE 強化¶
SFINAE の概念については Exceptional C++ あたりを参照すること。ほんとうの問題は私が SFINAE を必要とするコードを書く可能性があるかどうかだ。ないと思う。
See also
テンプレートのエクスポート機能を削除¶
ここは読まなくていい。
並行プログラミング¶
スレッドローカルストレージ¶
〈変数宣言の際に、記憶域として thread_local
キーワードを指定することで、スレッドごとの静的記憶域に変数が保持される〉ので、このような変数は static
であると考えるほうが理解しやすい。スレッド別に static
なわけだ。
乱数のほうのサンプルコード、改行文字を出力するコードを << std::endl
に書き換えて実行すると面白いことに改行されない。
ブロックスコープを持つ static
変数初期化のスレッドセーフ化¶
static
変数の初期化のときに暗黙的にロックがかかるようになったと解釈してよいだろうか。
その他¶
戻り値の型を後置する関数宣言構文¶
従来の関数宣言文法に加え、戻り値の型をとりあえず auto
と書いておいて、パラメーターリストの後に ->
を書いて最後に戻り値の型を(おそらく間接的な形式で)書く文法が追加された。
コンパイル時 assertion¶
static_assert()
が言語レベルでサポートされるようになった。Boost にあった類似機能が標準に昇格したのだろう。あちらはマクロだったと記憶している。
コンパイルオプションによっては第二引数を与えなければエラーになる。例えば
g++
ならば-std=c++17
が必要。つまり C++17 から引数がちょうど一個であるオーバーロードを追加したということか。static_assert()
はconstexpr
な何かを引数に取ると覚えておくといいだろう。
生文字列リテラル¶
Python でいう r'...'
に相当する仕様。SQL にもある。ユースケースも同じ。
C++11 では R"(...)"
という形で書く。括弧については変種を許す。
UTF-16 と UTF-32 の文字型¶
使いにくそう。
C++11 では新しく組み込み型
char16_t
とchar32_t
が加わる。u"..."
とU"..."
でそれぞれchar16_t
型配列とchar32_t
型配列のリテラルを宣言できる。
等々。Unicode の知識が要る。
UTF-8 文字列リテラル¶
u8
プレフィクスを付与した文字列リテラルがあるとする。これはコンパイル時に
UTF-8 に符号化される。
属性構文¶
二重の角括弧で囲まれた属性を C++ コードの構成要素に付与することで、コンパイラーが助かるらしい。今は [[noreturn]]
と [[carries_dependency]]
の二つしかない。
[[noreturn]]
は関数に対する属性であって、文字通り return
することがない関数に付与する。cpprefjp の例では、実行時に必ず例外が送出する関数に対して適用している。
[[carries_dependency]]
は難しい。並行プログラミングで用いられる。関数に付与するものと、引数に付与するものの二種類ある。あとでやろう。
演算子 alignas
- アライメントを指定する¶
C/C++ のコードを機械語レベルに落として考えるプログラマーならこの機能の役割はすぐにわかると思う。
〈構造体やクラスの宣言時に使用した場合、その型のインスタンス全てをアライメントする〉とある。例えば 28 バイトの構造体を定義しても、その型の変数を定義するとアドレスが 32 バイト境界に揃えられるということだろう。
このルールは構造体やクラスのメンバー変数についても適用される。
alinas(nn) typename identifier;
のような文法で指定する(ここで alinas
だけがキーワード)。
cpprefjp のサンプルコードは 4 バイト型の align を 1, 2 などで指定しようと試みていることに注意。そんなことはできないので結局 4 バイト境界揃えになる。
g++ (GCC) 10.0.2 では alinas(0)
は認められない。あるいはコンパイルオプションの指定が悪いのかもしれない。
演算子 alignof
- アライメントを取得する¶
演算子 alignof
は型に対して作用する。sizeof
が型にも変数にも作用するのとは違う。
さっきの演算子も含め alignment 関連の演算子は C++ ではなく C の仕様とするべきだと思う(たぶんそうなっている)。
cpprefjp のデモコードに対しては 32bit 環境での結果が掲載されている。64bit 環境で出力すると結果の一部が 4 から 8 になる。
小さな変更¶
更新された定義済みマクロ¶
読まなくていい。
機能テストマクロ¶
g++ (GCC) 10.0.2 では __has_cpp_attribute(carries_dependency)
が使えないようだ。
テンプレート再帰回数の制限緩和¶
読まなくていい。
キーワード typename
および template
の適用範囲拡大¶
cpprefjp のデモコードの関数 main
の二行とも、従来は何かのテンプレートの中でしか書けないコードだった。二行目は C++03 時代でもお目にかかったことがない文法だが。
入れ子の指定にグローバルスコープ ::
を付加する¶
cpprefjp の記事だけでは何のことか理解できないので、あとで仕様を見に行く。
前方宣言時要素数指定配列機能¶
これは試すのが面倒臭い。そしてこれも詳細がわからない(意味はわかる)。
名前空間 std::posix
および ::posix
¶
これらの名前空間が予約された。我々一般プログラマーはこの名前空間を定義してはならない。
演算子 sizeof
が非静的メンバー変数をオペランドにとれる¶
sizeof MyClass::my_member
のような書き方が許されるようになった。クラス
MyClass
のどのオブジェクトも sizeof my_member
の値は等しいのだから問題ない。
キーワード register
は廃止予定要素となった¶
いにしえの記憶域制御系キーワードだが、ついに廃止予定要素になった。遠い未来の C++
では auto
のように転生するかもしれない。
参照への参照はエラーではなくなった¶
テンプレートメタプログラミングでありがちなコンパイルエラーであったが、これが C++11 からコンパイラーは単なる参照として扱うようになる。
C99 互換機能¶
C++11 というより C の仕様なのでここでは細かく分析しない。
ノートにはとらないが
long long
の件はふつうに重要なので一読すること。64bit 固定とは意味が異なる。__func__
は Python にも似たものがある (__func__.__name__
) が、static const char*
型の変数として関数名をその関数のスコープの内部から参照できる。列挙子の末尾へのカンマ付加を許可。まさか
enum
しか認められていない?divmod 演算の丸め結果はだいじなので学習する。
a
,b
を負かもしれない整数型変数とする。いちおう断るがb != 0
とする。このとき値a / b
は整数であって、この除算を小数で考えるときに得られる商をゼロ方向に丸める値と等しいものとする。恒等式
(a / b) * b + a % b == a
を憶えること。