にわかにC++を勉強しないといけなくなったので忘れそうな点をメモ。
C++11前提、サンプルコードはg++ 4.8.1で動作確認。
文法
const
-
const int * const i;
: 左のconstはポインタの指示先の領域に書き込めないことを、右のconstはポインタ変数に書き込めないことを示す。 - constメンバ関数: メンバ変数を書き換えない意味でのconstは仮引数列の後に置く。
class Hoge {
int x;
public:
int getX() const {
return x;
}
};
比較可能なクラスを作る
#include<iostream>
#include<vector>
#include<algorithm>
class Hoge {
public:
int x;
Hoge(int x) {this->x = x;}
bool operator<(Hoge other) const{ // const必須なので注意!
return this->x < other.x;
}
};
int main() {
std::vector<Hoge> v = {2, 0, 1};
std::for_each(v.begin(), v.end(), [](const Hoge &hoge){std::cout << hoge.x << std::endl;}); // 2 0 1
std::sort(v.begin(), v.end()); // operator<に従ってソート
std::for_each(v.begin(), v.end(), [](const Hoge &hoge){std::cout << hoge.x << std::endl;}); // 0 1 2
}
friend関数を使う方法もあるが気に食わないので省略。
auto, decltype
これのおかげでけっこうC++が好きになりました。イテレータの型をごちゃごちゃ書かなくてよかったり、イイネ!
#include<iostream>
#include<vector>
int main() {
std::vector<int> v = {1, 2, 3};
for(auto iter = v.begin(); iter != v.end(); ++iter) { // iter++より++iterのほうがパフォーマンス上いいらしい
std::cout << *iter << std::endl; // 1 2 3
}
}
#include <iostream>
#include <boost/type_index.hpp>
template<typename T1, typename T2>
auto sum(T1 x, T2 y) ->decltype(x+y) {
std::cout << "T1:" << boost::typeindex::type_id_with_cvr<T1>().pretty_name() << std::endl;
std::cout << "T2:" << boost::typeindex::type_id_with_cvr<T2>().pretty_name() << std::endl;
std::cout << "ret: " << boost::typeindex::type_id_with_cvr<decltype(x+y)>().pretty_name() << std::endl;
return x+y;
}
int main() {
std::cout << sum('0', '1') << std::endl; // T1: char, T2: char, ret: int, 97
std::cout << sum((unsigned int)1, (unsigned int)1) << std::endl; // T1: unsigned int, T2: unsigned int, ret: unsigned int, 2
std::cout << sum((unsigned int)1, -2) << std::endl; // T1: unsigned int, T2: int, ret: unsigned int, 4294967295 (!!!!)
std::cout << sum(1, 1.1) << std::endl; // T1: int, T2: double, ret: double, 2.1
std::cout << sum(std::string("foo"), std::string("bar")) << std::endl; // foobar
}
範囲ベースfor文
javaにあるようなやつですね。
#include <iostream>
#include <vector>
int main() {
std::string str = "foobar";
std::vector<int> vec = {0, 1, 2};
for(auto i : str) {std::cout << i << std::endl;} // f, o, o, b, a, r
for(auto i : vec) {std::cout << i << std::endl;} // 0, 1, 2
}
typeid演算子
#include <iostream>
#include <typeinfo>
#include <vector>
int main() {
char c;
short s;
int i;
long l;
float f;
double d;
std::string str;
std::vector<int> v;
std::cout << typeid(c).name() << std::endl; // c
std::cout << typeid(s).name() << std::endl; // s
std::cout << typeid(i).name() << std::endl; // i
std::cout << typeid(l).name() << std::endl; // l
std::cout << typeid(f).name() << std::endl; // f
std::cout << typeid(d).name() << std::endl; // d
std::cout << typeid(str).name() << std::endl; // Ss
std::cout << typeid(v).name() << std::endl; // St6vectorIiSaIiEE
}
ラムダ式
STLアルゴリズムと組み合わせて使うと気持ちが良い。auto型の変数に代入もできるよ!
より詳しい説明は、ここがgood。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int a[10];
std::generate(a, a+10, [](){static int i = 0; return i++;});
std::for_each(a, a+10, [](const int &i){std::cout << i << std::endl;}); // 0 1 2 ...
std::sort(a, a+10, [](const int &i, const int &j) {return i > j;}); // 逆順ソート。
std::sort(a, a+10, std::greater<int>()); // 上の行と同じ。 std::less<int>() もある
std::for_each(a, a+10, [](const int &i){std::cout << i << std::endl;}); // 9 8 7 ...
auto print = [](std::string x){std::cout << x << std::endl;};
print("hoge"); // hoge
}
キャスト
- static_cast: 暗黙型変換が存在する場合のみ有効。それ以外はコンパイルエラー。cv(const, volatile)修飾子をつけることはできるが外すことはできない。
- dynamic_cast: 実行時にキャスト可かどうか判断。キャストできない場合ポインタならNULLが返り、参照なら
std::bad_cast
がthrowされる。dynamic_castが可能かどうかは実行時に判断されるのでパフォーマンスには注意が必要。
-- 基本使ってはいけない壁 --
- const_cast: cv修飾子を外せるのは const_cast 先生だけ!良い子はconstを外しちゃだめだよ!
- reinterpret_cast: ポインタと参照については、違う型のポインタまたは参照への変換ができる(危険!)。ポインタと整数の相互変換もできる。
#include <iostream>
int main() {
const double d = 12.345;
const double &rd = d;
std::cout << static_cast<int>(d) << std::endl; // 12。暗黙の型変換が存在するので合法
std::cout << std::hex << *reinterpret_cast<const unsigned long *>(&d) << std::dec << std::endl; // 4028b0a3d70a3d71 = 12.345 を表すビット列
std::cout << std::hex << reinterpret_cast<const unsigned long &>(rd) << std::dec << std::endl; // 同上
double *pd = const_cast<double *>(&d); // const 修飾子を外す: やってはいけない
*pd = 54.321; // const だったはずの領域に無理やり書き込んでしまった
std::cout << *pd << std::endl; // 54.321
std::cout << d << std::endl; // 私の処理系では 12.345。恐らくコンパイル時に値が固定されているため。
}
アクセス制御と継承
メンバのアクセス制御
- private: 子孫にも他人にも絶対見せない。友達には見せる。
- protected: 友達と子供には見せる。孫には自分からは見せないが、子が孫に見せるかも。
- public: 誰にでも見せる。
継承修飾子
- private: 自分のpublicメンバもprotectedメンバも子のprivateメンバとする
- protected: 自分のpublicメンバもprotectedメンバも子のprotectedメンバとする。
- public: 自分のpublicメンバは子のpublicメンバ、自分のprotectedメンバは子のprotectedメンバとする。
修飾子を省略した場合は、派生クラスがclassキーワードで宣言されている場合はprivate継承、structキーワードで宣言されている場合はpublic継承となる。
もちろんやるべきではないが、classをstructで継承することもできるようだ。(clang6.0で確認)
コンストラクタ
- 基底クラスから継承したデータメンバの初期化は直接基底クラスのコンストラクタに委ねるのが原則。
#include <iostream>
class Parent {
int i;
public:
Parent(int i):i(i){};
};
class Child : Parent {
int j;
public:
Child(int i, int j):Parent(i), j(j){};
};
int main() {
Child c(1, 2);
Parent p = c; // slicing : Parent クラスのデータメンバだけがコピーされる
}
仮想関数とdynamic_cast
- 非仮想関数は静的結合、仮想関数は仮想関数テーブルによる動的結合
- ダウンキャストは基本不許可だが、オブジェクトの実体がダウンキャスト先の派生クラスの場合は可
#include <iostream>
class Parent {
public:
void func1() {std::cout << "My static type is parent" << std::endl;};
virtual void func2() {std::cout << "My dynamic type is parent" << std::endl;};
};
class Child : public Parent {
public:
void func1() {std::cout << "My static type is child" << std::endl;};
void func2() {std::cout << "My dynamic type is child" << std::endl;};
};
int main() {
Parent *p1 = new Child();
Parent *p2 = new Parent();
Child *c1 = dynamic_cast<Child *>(p1);
Child *c2 = dynamic_cast<Child *>(p2);
p1->func1(); // My static type is parent
p1->func2(); // My dynamic type is child
p2->func1(); // My static type is parent
p2->func2(); // My dynamic type is parent
c1->func1(); // My static type is child
c1->func2(); // My dynamic type is child
// c2->func1(); // segmentation fault, because c2 is NULL
// c2->func2(); // segmentation fault, because c2 is NULL
}
多重継承
- 基底クラスのコンストラクタの呼び出し順は基底指定子の並び順
- 基底クラスのメンバ名がかぶったら::で明示的にどっちかを指定
- クロスキャスト: dynamic_cast演算子で多重継承の基底クラス間でのキャストが可能。
#include <iostream>
class Base1 {
public:
Base1(){std::cout << "Initializing Base1" << std::endl;}
void func() {std::cout << "I am Base1" << std::endl;};
void func1() {std::cout << "Base1 specific implementation" << std::endl;}
virtual void vfunc1() = 0;
};
class Base2 {
public:
Base2(){std::cout << "Initializing Base2" << std::endl;}
void func() {std::cout << "I am Base2" << std::endl;};
void func2() {std::cout << "Base2 specific implementation" << std::endl;}
virtual void vfunc2() = 0;
};
class Derived : public Base1, public Base2{ // コンストラクタの呼び出し順は基底指定し並びの宣言順
void vfunc1() {std::cout << "Derived implementation of vfunc1" << std::endl;};
void vfunc2() {std::cout << "Derived implementation of vfunc2" << std::endl;};
};
int main() {
Derived derived; // Initializing Base1, Initializing Base2
derived.Base1::func(); // I am Base1
derived.Base2::func(); // I am Base2
Base1 *pb1 = new Derived();
pb1->func1(); // Base1 specific implementation
pb1->vfunc1(); // Derived implementation of vfunc1
Base2 *pb2 = dynamic_cast<Base2 *>(pb1); // cross cast
pb2->func2(); // Base2 specific implementation
pb2->vfunc2(); // Derived implementation ov vfunc2
}
仮想継承というのもあるらしいがややこしすぎて撃沈。
explicit コンストラクタ
引数を1つだけとるコンストラクタを変換コンストラクタ(converting constructor)と呼ぶ。
変換コンストラクタをexplicit宣言することで暗黙の型変換を禁止できる。
関数ポインタ
大部分はC++というかCの話だが、よく書式を忘れる。
#include <iostream>
int _single(int i) {return i * 1;}
int _double(int i) {return i * 2;}
int _triple(int i) {return i * 3;}
class Hoge {
int i;
public:
Hoge(int i): i(i) {};
int _single() const {return i * 1;}
int _double() const {return i * 2;}
int _triple() const {return i * 3;}
};
int main() {
int (*funcPtr)(int) = _single;
int (*funcPtrs[])(int) = {_single, _double, _triple};
std::cout << funcPtr(1) << std::endl; // 1
for(int i = 0; i < 3; i++) {
std::cout << funcPtrs[i](1) << std::endl; // 1, 2, 3
}
// typedef するのが一般的
typedef int(*FP)(int);
FP fp = _double;
FP fps[] = {_double, _triple, _single};
std::cout << fp(1) << std::endl; // 2
for(int i = 0; i < 3; i++) {
std::cout << fps[i](1) << std::endl; // 2, 3, 1
}
// クラスのメンバ関数への関数ポインタ
typedef int(Hoge::*FPHoge)() const;
FPHoge fph = &Hoge::_triple;
FPHoge fphs[] = {&Hoge::_triple, &Hoge::_single, &Hoge::_double};
Hoge hoge(100);
std::cout << (hoge.*fph)() << std::endl; // 300
for(int i = 0; i < 3; i++) {
std::cout << (hoge.*fphs[i])() << std::endl; // 300, 100, 200
}
Hoge *phoge = new Hoge(200);
std::cout << (phoge->*fph)() << std::endl; // 600
for(int i = 0; i < 3; i++) {
std::cout << (phoge->*fphs[i])() << std::endl; // 600, 200, 400
}
}
その他tips
- (C++11) ヘッダにビット幅指定の整数型の定義がある。std::int8_tなど
- (C++11) スコープ付き enum 名前空間の衝突を enum struct Hoge{A, B, C};と宣言しHoge::Aとして使用することで回避できる。
標準ライブラリ
iostreamの桁数指定
#include <iostream>
#include <iomanip>
int main() {
std::cout << std::setw(2) << std::setfill('0') << 2 << std::endl; // 02
std::cout << true << ", " << std::boolalpha << true << ", "
<< std::noboolalpha << true << std::endl; // 1, true, 1
std::cout.setf(std::ios_base::fixed);
std::cout << std::setprecision(2) << 1.111111 << std::endl; // 1.11
std::cout << "0x" << std::setw(8) << std::hex << 65535 << std::dec << std::endl; // 0x0000ffff
}
chrono での処理時間計測
#include <iostream>
#include <chrono>
int main() {
auto start = std::chrono::system_clock::now();
// 重い処理
auto end = std::chrono::system_clock::now();
auto elapsed = end - start;
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count() << std::endl; // ms 単位
}
コレクションの和
#include <iostream>
#include <algorithm>
int main() {
int a[10];
std::generate(a, a+10, [](){static int i = 0; return i++;}); // 0, 1, 2, ..., 9
std::cout << std::accumulate(a, a+10, 0, [](int a, int b) {return a+b;}); // 45
}
乱数
#include <iostream>
#include <random>
int main() {
std::random_device rand; // ハードウェア由来の非決定論的乱数を(使えれば)使う。多少遅い。
std::mt19937 mt(rand()); // メルセンヌツイスタ (Mersenne twister)
std::cout << std::hex << mt() << std::dec << std::endl; // 一様分布、範囲は [0, 0xffffffff]
std::uniform_int_distribution<int> dice6(1, 6);
std::cout << std::hex << dice6(mt) << std::endl; // 離散一様分布(discrete uniform distribution)、範囲は [1, 6]
std::uniform_real_distribution<double> continuous(0.0, 1.0);
std::cout << std::hex << continuous(mt) << std::endl; // 連続一様分布(continuous uniform distribution)、範囲は [0, 1)
std::normal_distribution<double> normal(0.0, 1.0); // コンストラクタの引数は (mean, stdiv)
std::cout << normal(mt) << std::endl; // 標準正規分布 (standard normal distribution)
}
正規表現
#include <iostream>
#include <regex>
int main() {
std::string str = "foo0bar1";
std::regex re("(.+?)(\\d)");
std::smatch result;
if(std::regex_search(str, result, re)) {
for(auto g : result) {
std::cout << g << std::endl; // foo0, foo, 0 (マッチ全体, 各group)
}
}
std::cout << std::regex_replace(str, re, "$2$1") << std::endl; // 0foo1bar
}