C++11以降のC++はModern C++と言われ、それ以前のC++(古典的C++)に対して多くの言語仕様が追加された。古典的C++しか知らないプログラマがModern C++の言語仕様を使い倒したコードを見ても、さっぱり分からないかもしれない。
古典的C++を主戦場とするプログラマに対して、盲目的にModern C++に移行すべきだと言うつもりはない。ただ、Modern C++は古典的C++の上位互換であり、古典的C++の枠組みで書かれたコードに対してModern C++の一部の仕様をつまみ食い的に利用するだけでも、おいしいことはある。
そこで、古典的C++の枠組みでコードを書くプログラマがつまみ食いする価値のあるModern C++の仕様について、いくつかの記事に分けて紹介していく。
アトミック操作
C++11では、マルチスレッドでアトミックに変数にアクセスする機能がある。
std::atomic<変数の型> 変数名; のように変数を宣言すれば、その変数はアトミックにアクセスできる。
下記は、マルチスレッドで0から9999までの数字を順番に出力するプログラムで、変数Aへのアトミックなアクセスが保証されていれば、値の重複や抜けは発生しない。
//#define ENABLE_ATOMIC // アトミック性を保証する時は有効化する
#include <stdio.h>
#include <pthread.h>
#ifdef ENABLE_ATOMIC
#include <atomic>
std::atomic<int> A;
#else
int A;
#endif
static void* start_routine(void*) {
for (int loop = 100; loop !=0; loop--){
int a = A++;
printf("%d\n", a);
}
return nullptr;
}
static const int NUM_THREAD = 100;
int main() {
printf("\n");
A = 0;
pthread_t th[NUM_THREAD];
for (int i = 0; i < NUM_THREAD; i++) {
pthread_create(&th[i], nullptr, start_routine, nullptr);
}
for (int j = 0; j < NUM_THREAD; j++) {
void* retval;
pthread_join(th[j], &retval);
}
return 0;
}
上記プログラムをENABLE_ATOMICマクロを定義せずにコンパイルして実行すると、出力される値が重複することが起きる。複数のスレッドからの変数Aへのアクセスが競合してしまうことがあると考えられる。
$ g++ -std=c++11 atomic.cc -lpthread -O0
$ ./a.out | sort -n | uniq -D
5166
5166
$ ./a.out | sort -n | uniq -D (たまたま競合していない場合)
$ ./a.out | sort -n | uniq -D
5214
5214
$ ./a.out | sort -n | uniq -D (たまたま競合していない場合)
$ ./a.out | sort -n | uniq -D
6896
6896
6961
6961
ENABLE_ATOMICマクロを定義してこのプログラムをコンパイルして実行すると、何回実行しても出力される値が重複することは起きず、変数Aへのアトミックなアクセスが行われていることが分かる。
$ g++ -std=c++11 atomic.cc -lpthread -O0 -DENABLE_ATOMIC
$ ./a.out | sort -n | uniq -D
$ ./a.out | sort -n | uniq -D
$ ./a.out | sort -n | uniq -D
$ ./a.out | sort -n | uniq -D
$ ./a.out | sort -n | uniq -D
$ ./a.out | sort -n | uniq -D
$