読者です 読者をやめる 読者になる 読者になる

藻ログ

都会でOLをしています

C++ゆとり用

世の中にはわたしのようなライトC++書き向けの資料が不足しているので,
大学院での普段使い*1用途で収集したものをまとめてみました.

clangを使う

Clang - Wikipedia
gccよりもclangの方がC++ 規格に準拠しているとうたわれています. また, コンパイルもちょっと速い. *2

標準ライブラリを使う

 ∧∧∧∧∧∧∧∧∧
< STL!STL!STL!STL! >
 ∨∨∨∨∨∨∨∨∨
 _     _
`/っ)    /っ)
/ / ∧_∧ / / ∧_∧
\\(   )\\(   )

Standard Template Library - Wikipedia

STLではコンテナとアルゴリズムが分離されており,
用途に応じて適切なデータ構造(コンテナ)に格納されたデータに対してアルゴリズムを適用します.

[STL] Containers

vector,map,list,dequeueなど
hashが見当たりませんが, unorderd_mapが一応それに相当します.
http://www.cplusplus.com/reference/stl/

[STL] Algorighm

sort, find, for_each, findなど基本アルゴリズム

#include <algorithm>

http://www.cplusplus.com/reference/algorithm/

[STL] Numeric

たまにどっちにあるのか混乱しますが,
accumulate, partial_sumとかはnumericにあります

#include <numeric>

http://www.cplusplus.com/reference/numeric/

[STL] stream

iostream,fstreamやstringstreamなど,ほげほげstream群も便利

int toInt(std::string s) { int r = 0; std::istringstream ss(s); ss >> r; return r; }
string toStr(int n) { std::ostringstream ss; ss << n; return ss.str(); }

追記
C++11から, std::to_string/std::stoi, stod... が登場した模様 *3
std::stoi, std::stol, std::stoll - cppreference.com
std::to_string - cppreference.com

STLコンテナの使い分け

各コンテナの性質を理解せずに使っているとパフォーマンスが落ちることがあります.
オーダーを理解して適切なコンテナとアルゴリズムを選択しましょう.
[C++] STLの型の使い分け - Qiita

ヘッダファイルでusing namespaceする罪人よ

using namespace std;

ヘッダーを読み込んだファイルが汚染されます. やらかすと殴られます.
たとえばOpenCVはcv::vectorがあってstd::vectorとややこしいので, STL以外にライブラリを使うときは名前空間を常に明示するようにしています.

std::vector<cv::Mat> images;

特にstd名前空間はめちゃくちゃ広大で危険なので*4注意したい.


&参照を使う

余計な一時オブジェクトを作るとメモリが無駄なので, なるべく参照渡しします.

void hogehoge(const std::vector<int>& vec) {
  std::cout << vec.size() << std::endl; // 実体のように扱えるが, 参照
}

Cだと class* obj とかポインタで受け取ってobj->propのようにアクセスしたり,
実体(コピー)を受けてobj.propのようにアクセスしたりでしたが, &参照が出来るようになって扱いやすくなりました. データを読み出し専用にしたい場合は, const宣言を忘れずに.

constを使う

constのお陰で安心してC++が書けます.

class GeodesicDome {
  private:
    const double radius;
    const int split_depth;
  public:
    std::vector<double> vertices;

  public:
    GeodesicDome(double radius, int split_depth) :
                  radius(radius),
                  split_depth(split_depth) {};
    void createFromIcosaHedron();
    void render() const;
    void dumpStatus() const;
    void output(const std::string& fname) const;
};

変更したくないオブジェクトにはconstを付加し, インスタンス変数を変更しないメソッド宣言末尾にもconstを付加します. こうすると違反をした時にコンパイラが怒ってくれます.
クラスのconstなメンバ変数は, 初期化子で初期化してやる必要があります.

GeodesicDome(double radius, int split_depth) :
             radius(radius),
             split_depth(split_depth) {};

また, constなポインタは記述が特殊なので注意が必要です.
結論から言うと

const char* const str="hogehoge";

と書くのが正しいのですが, イミフなのでもうちょっとみてみます.

int target = 42; // 指し示したいやつ
const int*       ptr1 = &target; 
      int* const ptr2 = &target; 
const int* const ptr3 = &target;

とすると,*ptrとすれば42が返ってくるのですが,

// ポインタが指し示す先のデータを書き換えようとする
*ptr1 = 256; // ERROR
*ptr2 = 256; // OK
*ptr3 = 256; // ERROR
// ポインタを書き換えようとする
int dst = 1024;
ptr1 = &dst; // OK
ptr2 = &dst; // ERROR
ptr3 = &dst; // ERROR

となるので, ポインタ型宣言の左側のconstはポインタが指すデータをconstとし,
ポインタ型宣言の右側のconstはポインタに格納されているアドレスをconstとすると解釈できます.

constは外せる

const_castでconstを取ることができます.
無論, フリーダムにconst_castを使用するとプロダクトが崩壊するので, やむを得ない場合に使います.
利用例

void legacyFunc(char* str);
...
const char* input="const characters";
legacyFunc(const_cast<char*>(input));

10/02 利用例がそもそもおかしかったので修正しました

inline展開

inline宣言すると呼び出し元に展開されるので, 関数呼び出し時のオーバーヘッドを抑えることができます. 短くて何度も呼び出される関数はinline宣言にすると良いです.

inline double hoge() {
  //なんかシンプルな処理
  return xxx;
}

コンパイラの最適化オプションについては
http://www.cqpub.co.jp/interface/column/freesoft/2002/200208/08-4.htm
展開すればするほど実行ファイルのサイズは大きくなり, コンパイルも遅くなるので注意します.

C++11の機能を使う

C++11 - Wikipedia

$ g++ -std=c++11
$ clang++ -std=c++11

コンパイルできます. 以下はC++11規格で使用可能になった機能の一部です

[C++11] auto

型推論ができるようになりました.

auto x = 42; // int
auto y = 3.14; //float

std::vector<int>iterator it = v.begin() // C++03
auto it = v.begin() // C++11

もう長ったらしいイテレータを記述しなくて良いですね.

[C++11] foreach

std::vector<double> vec = { 1,2,3,4,5,6 };
for (const auto& v: vec) {
  std::cout << v << std::endl;
}
for (const int x: { 1,2,3,4,5,6 }) std::cout << x << std::endl;

参照&無しで受けると要素をdeep copyで受けます.

[C++11] initializer list

上で出てるけど, 要素の初期化がとても楽になりました.

std::vector<int> v0 = { 1,0,0 };
std::vector<int> v1 = { 1,0,0 };
std::vector<int> v2 = { 1,0,0 };
std::vector<std::vector<int>> v3 = { v1,v2,v3 };
std::vector<std::vector<int>> v4 = { {1,2,3}, {1,2}, {1} };

コンストラクタにも.

return std::vector<int>({1,2,3});

[C++11] array

固定長配列が仲間入りしました.

int ary[5] = {1,2,3,4,5}; // いままでの
int ary[] = {1,2,3,4,5}; // いままでの
std::array<int,5> ary= {1,2,3,4,5}; // C++11 std::array

09/17 追記
可変長配列を扱うstd::vectorは動的確保を行うのに対して, 固定長配列を扱うstd::arrayは静的確保なので,
int* ary = new int[5]; // 動的確保
は比較として正しくないという指摘を受けて修正しました.

私は殆ど使ってないのですが, 恩恵は, STLコンテナ特有のメソッド(beginやendとか)が使えるようになって扱いやすさが向上&生ポインタを触らなくてよいとかでしょうか.
静的配列なのでvectorよりもメモリ周りの処理がない分高速?

// 今まで
int ary[5] = { 9,1,3,4,2};
std::sort( ary, ary+5 ); // 絶対書きたくない

// C++11
int array<int,5> ary = {9,1,3,4,2};
std::sort(ary.begin(), ary.end()); // GOOD

[C++11] nullptr

C C++
NULL (void*)0 0

CではNULLはvoid*でしたが, C++ではマクロで0になってるので謎挙動の原因になります.
関数オーバーロードで引数が異なるものがあるとき

void hogehoge(char* foo, int bar);
void hogehoge(char* foo, void* bar);

第2引数でNULLをえいやと渡すと, intと解釈されてvoid hogehoge(char* foo, int bar);
の方が呼び出されてしまうので,

hogehoge(_foo, (void*)NULL);

とかvoid*キャストして頑張ってましたが, nullptrを渡せばよくなりました.
NULLはマクロで定義された0ですが, nullptrはぬるぽであることをコンパイラに教えることができます.

[C++11] static_assert

cassertは実行時評価ですがstatic_assertはコンパイル時評価です.
第2引数で文字列をとれるので, ANDで文字列を渡すという変なハックをしなくて良いです

assert(a==b && "[ERROR] a is not equal to b"); // 実行時
static_assert(a==b, "[ERROR] a is not equal to b"); //コンパイル時

[C++11] 暗黙的コピーコンストラクタの禁止

private宣言するとか頑張らなくても, deleteを指定すればコピーコンストラクタの使用を禁止できるようになりました.

class NoCopy {
NoCopy(const NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
}

[C++11] ラムダ

ラムダ関数が定義できるようになりました.

[](int x, int y) -> int { int z = x + y; return z + x; }
[](int x, int y) { int z = x + y; return z + x; } // return val; の場合は戻り値型を省略できる

[]内は

[] ラムダ関数外のどの変数も使うことができない。
[x, &y] xはコピーされる。yは参照渡しされる。
[&] すべての外部変数は、もし使われれば暗黙的に参照渡しされる。
[=] すべての外部変数は、もし使われれば暗黙的にコピーされる。
[&, x] xは明示的にコピーされる。その他の変数は参照渡しされる。
[=, &z] zは明示的に参照渡しされる。その他の変数はコピーされる。

のように扱えます.

今まで外部関数を宣言していたのが, スマートに書けるように.
これでPerlとかでいうmapとかgrepも, STLアルゴリズムラムダ式を放り込んでかけちゃう?*5

#!/usr/bin/perl
my $prices = [ 98, 4980, 12500 ];
my $pay = [ map { $_ * 1.08 } @$prices ]; #=> [105.84,5378.4,13500]
my $found = [ grep { $_ > 1000 } @$prices ]; #=> [4980,12500]

mapはこんな感じか

// map func
const double scale = 1.08;
std::transform(v.begin(), v.end(), result.begin(),
               [scale](int x) -> double {return x*scale }); 

全然使いこなしてないので, 色んなユースケースを読んでみたい
本の虫: lambda 完全解説


boostを使う

Boost C++ Libraries

HSTL (HOBO Standard Template Library) ほぼ標準テンプレートライブラリ*6

# Mac OS X
$ brew install boost --c++11

# Linux (Ubuntu)
$ sudo apt-get install libboost-dev
$ sudo apt-get install libboost-xxx # 使いたいライブラリを適宜いれる

まとめて入れたければ libboost-all-devもある模様
Ubuntu – precise の libboost-all-dev パッケージに関する詳細

普段使いにはboost_program_options*7, boost_system などが入って行きやすいです.
boost_regexも使っていましたがC++11からstd::regexが追加されたので使わなくなりました.
http://www.cplusplus.com/reference/regex/
周囲ではboost_threadも使っている人が多いでしょうか.

boostにあったような機能がC++の新規格に組み込まれて標準化され...というのが結構あるので, 試験的ライブラリ群と見ることも
https://sites.google.com/site/boostjp/tips/cxx11-boost-mapping

splitとかいつも自作で悲しいのだが, STLで何故かみあたらない...(実はあるのか・・?)
boostにはstring_algorithmが一応あります.
http://www.boost.org/doc/libs/1_56_0/doc/html/string_algo.html

その実装、もうあります

車輪の再発明を避けます. CやC++だとなんでも自作しがちですが, 目的の実装は先人が既に実現しているはずなのです.
標準ライブラリとboostから検索だ!!

C++はなるべく書かない

お前ここまで来て何いってんだよという感じですが, 研究ではC++はなるべく書かないようにしています.
勿論どうしてもC++でないと実装できない箇所はC++で作ります.
ただ, LLで書いた方が遥かにコストが低いパーツが多いので, C++で作る部分はコマンドラインオプション*8を受け取ってデータシートを吐く最小の機能にとどめて, LLでラップして回したりしています*9.
後に変更を加えたい時に, C++記述部に変更を加えて全体の再コンパイルを要するのと, LLで記述した部分を変更して再実行すれば良いのとでは, 多くの場合後者の方が楽でしょう.

リファレンス(vim用)

おお, こんな便利なものが...知らなかった

GitHub - rhysd/unite-n3337: A vim plugin for unite.vim to look in N3337 quickly


GitHub - osyo-manga/unite-boost-online-doc

タブ設定

自分はソフトタブ派ですが, C++のデフォルト幅がスペース4で読み辛いので
エディタの設定で*.cpp *.hのタブ幅をスペース2個にしています.

" vimの場合の設定
augroup IntentGroup
  autocmd!
  au BufNewFile,BufRead *.cpp set nowrap softabstop=2 shiftwidth=2
  au BufNewFile,BufRead *.cc set nowrap softabstop=2 shiftwidth=2
  au BufNewFile,BufRead *.h set nowrap softabstop=2 shiftwidth=2
  au BufNewFile,BufRead *.hpp set nowrap softabstop=2 shiftwidth=2
augroup END

あっ

スマートポインタのこととか全然書いてない.....................................

雑談

C++に限らないけど, コールバック関数で固定引数を一部使わずに放置すると
Warning: unused variable xxx:
が出て鬱陶しいときがあります.

# define UNUSED_VARIABLES(x) (void)(x) 

そういうときは↑みたいなマクロを定義してvoidキャストで消費してみたりするのだが, 雑なので良い方法が知りたい

09/25 追記 ---
職業としてのプログラミング 使用しない仮引数
なるほど!!!!

参考

Effective Modern C++

C++の古典的名著といえばEffecive C++ ですね.

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

ただ, C++11,14規格によって古くなってしまった内容も多く
著者のスコットメイヤーズもこんなことを↓言っていました
The View from Aristeia: Effective C++ in the C++0x (C++11) Age

そのメイヤーズさんが満を持してEffective Modern C++を発売したようです. これは読まねばなるまい.

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14

*1:どんな普段だよ

*2:と思っていたが実際は抜きつ抜かれつしているらしい.

*3:なんか問題があってto_intかitosにならなかったんでしょうか....

*4:いろいろあって機能分割されてないらしい

*5:grepはequal_toとかで元々書けた疑惑

*6:造語です. この略称は実在しません.

*7:イケてるgetoptsみたいなやつ

*8:boost_program_options でインタフェースは用意しておく

*9: LLとのやりとりはboost_pythonやCythonを使っても良い