MyEnigma

とある自律移動システムエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

ROSユーザのための便利でよく使うC++11の機能まとめ

 

目次

はじめに

Pythonなどでコードを書いていると、

久しぶりにROSでC++でコードを書こうとすると、

その冗長性にいやになる時があります。

 

しかし、C++11という

2011年に制定されたISO基準の機能を利用すると、

C++でもかなり簡潔にコードを書くことができます。

 

C++11には非常に多くの追加機能がありますが、

その中でもC++のライトユーザーに便利で、

自分が良く使用する機能をピックアップして紹介したいと思います。

 

 

コンパイル時にC++11の機能をONする

gccでコンパイル時にC++11の機能をONする場合は、

下記のようにコンパイルオブションを指定します。

$ gcc test.cpp -std=c++11

 

ROSでc++11を使えるようにする方法

CMakeLists.txtに下記の一文を追加すればOK

set(CMAKE_CXX_FLAGS "-std=c++0x ${CMAKE_CXX_FLAGS}")

 

STLのコンテナの初期化

C++11からは、

STLのコンテナの初期化を

下記のように

波括弧を使って統一的に実施できます。

vector<int> v{ 0, 1, 2, 3 };
list<int> l{1,1,1,1,1};
deque<int> d{2,2,2,2,2};

通常の配列の初期化も同じようにできますが、

個人的に配列嫌いなので紹介は省略します。

 

Foreach

pythonで言う所のinを使ったforループが、

C++11でも簡単にできます。

下記のように、

コロンでコンテナと要素変数を結んで、

for文に入れるだけです。

vector<int> v{ 0, 1, 2, 3 };
for(int iv : v){
    cout<<iv<<endl;
}

インデックスを使った場合の

最大値のミスなどがなくなるので、

非常に便利です。

 

また添字でアクセスできない

listなどにも、イテレータを使わずに

簡単にアクセスできるため、便利です。

list<int> l{1,1,1,1,1};

for(int iv : l){
 cout<<iv<<endl;
}

 

autoによる型推論

autoという型を使うことで、

コンパイラが自動で型を設定してくれます。

 

stlのイテレータは以前は非常に長くて、書くのが面倒でしたが、

autoを使うことで下記のように、

非常に簡単にイテレータを書くことができます。

// before
for (std::vector<int>iterator it = v.begin(), end = v.end(); it != end; ++it) {}

// after
for (auto it = v.begin(), end = v.end(); it != end; ++it) {}

 

タプル

C++11ではPythonなどでおなじみのタプルが

stdに追加されました。

 

下記のように、ちょっとしたデータ群をまとめて取扱いたい時に、

これまではclassやstructを作っていましたが、

それが不要になります。

下記の例のように、関数が複数の返り値を返したい時にも

便利ですね。

 

また一つ注意点としてはmake_tupleとtieは両方共、

タプルを作るメソッドですが、

make_tupleは引数のコピーでタプルを作り、

tieは引数の参照でタプルを作ります。

タプルが変更不可能なデータ集合だと考えると、

tieで作っておいた方が、パフォーマンスが良いのかもしれません。

またtieを使うことで複数の個別の変数に一行でタプルの中身を展開できます。

#include<iostream>
using namespace std;

tuple<int, float> GetTuple(){
  return make_tuple(1, 5.4);
}


int main(void){
  cout<<"hello world"<<endl;
  tuple<int, char, string> t = make_tuple(1, 'a', "hello");

  int i = get<0>(t);
  std::cout << i << std::endl;

  string s = get<2>(t);
  cout << s << endl;

  tuple<int, float> ret = GetTuple();

  cout<< get<0>(ret) <<endl;
  cout<< get<1>(ret) <<endl;

  string str = "test";
  double d = 3.0;

  // tieでタプルを作ると引数の参照でタプルを作る
  // make_tupleはコピー
  auto t2 = tie(str,d);
  cout<< get<0>(t2) <<endl;
  cout<< get<1>(t2) <<endl;


  //tieを使うと一気に個々の変数に展開できる
  std::tuple<int, char, std::string> t3(1, 'a', "Hello");

  int a = 0;
  char b = 0;
  string c;
  tie(a, b, c) = t3;

  cout << a << endl;
  cout << b << endl;
  cout << c << endl;

}

 

文字列と数値の変換

下記のように、文字列と数値の変換を

それぞれ標準ライブラリで実施できるようになりました。

//数字->string
string s=to_string(30);

//string->数字
int y = stoi("1945");
double a = stod("5.4");

ただ、名前の対称性が無いので覚えにくい。。。

 

Enumの最後にカンマをつけても良い

以前のenumは

要素の最後の後ろにカンマを付けた場合は、

コンパイルエラーになっていましたが、

C++11では最後の要素の後ろにカンマを付けてもOKになりました。

enumの要素を追加したりしても、

最後の要素のカンマを気にしなくてもよくなります。

enum Alphabet{
  A,
  B,
  C,
};

関数の引数も同じように

最後の引数の後ろにカンマを付けても良いようになってほしい。。。

 

型指定enum

こちらもenum関連の話ですが、

C++11からenumの型が指定できるようになりました。

 

これまでのenumの各要素はintの値に割り振れれていたようですが、

C++11から下記のように明示的にenumの型を指定できるようになりました。

enum SensorID: uint8_t{
  laser =0x01,
  camera=0x02,
  IMU   =0x03,
};

enumハックを使ってIDなどを管理したいときに非常に便利だと思います。

 

クラスメンバの初期化

以前のC++では、クラスメンバの初期化は、

初期化子を使っていましたが、

宣言とデータの代入を別々に書く必要があり、

ちょっと冗長でした。

 

しかし、C++11では、

下記のように、メンバ変数の宣言と同時に、

初期値の代入が可能になります。

非常に便利ですね。

class Person {
  public:
    int    id_ = 20;
    string name_="Tom";
};

int main(){ 
  Person person;
  cout<<"Person"<<person.id_<<endl;
}

 

コンテナへのデータ追加を高速化するemplace

vectorなどにクラスのオブジェクトデータを

push_backする際に、

クラスオブジェクトを生成するのと、

格納するのを同時に実施する時には、

push_back()ではなく、

emplace_back()を使うと、

クラスオブジェクトのコピーと廃棄が実施されずに、

計算コストを少なくすることができます。

 

下記のように、

オブジェクトを生成して、

即座に格納したい場合に、

push_backだとコピーコンストラクタが呼ばれますが、

emplace_backでは呼ばれないため、

計算が早くなります。

#include <iostream>
#include <vector>
using namespace std;

class Sample{
public:
  Sample(int a){
  }

};

int main(){
  cout<<"emplace sample"<<endl;
  vector<Sample> vec;

  vec.push_back(1);

  //結果は同じだがこっちのほうが早い
  vec.emplace_back(1);

}

   

乱数発生標準ライブラリ

C++元々の乱数発生関数rand()は、

分布が選べなかったり、

乱数としての性能が低いことで使いにくかったですが、

C++11から導入された乱数発生標準ライブラリを使うと、

様々な乱数発生を簡単に、かつ高精度に実施することができます。

 

実際の使い方は非常に簡単で、

下記のように乱数のシード(random_device)を使用し、

乱数発生器のオブジェクトを生成し

(mt:下記のサンプルではメルセンヌ・ツイスタ)

あとは、乱数の種類と範囲を指定するだけです。

# include <iostream>
# include <random>
using namespace std;

int main()
{
  //乱数の初期化
 random_device rd;
 mt19937 mt(rd());

  //=======一様分布=======
  //整数の一様分布
 uniform_int_distribution<int> dice(1,6);
 for(int i=0; i<10; ++i){
    cout << dice(mt) << endl;
 }

  //浮動小数点の一様分布
 uniform_real_distribution<double> score(0.0,10.0);
 for(int i=0; i<10; ++i){
    cout << score(mt) << endl;
 }

  //=======正規分布=====
  double mean=100;
  double std=1.0;
  normal_distribution<double> dist(mean,std);
  for(int i=0; i<10; ++i){
    cout << dist(mt) << endl;
 }
}

ロボティクスでは乱数を使うことが多いので

非常に便利だと思います。  

 

時刻関連標準ライブラリ

C++に時間関連の関数は、

これまでOS依存のものが多かったですが、

C++11で導入された

時刻関連標準ライブラリを使うと、

プラットフォームに依存しない

C++ソフトウェアを書くことができます。

 

下記のように、time_point型で時間を格納する変数を作成し、

あとは、now()関数で時間を計算することができます。

経過時間などは、time_point型で引き算することで、

計算でき、その単位もduratin_castで簡単に指定できます。

 

#include <iostream>
#include <chrono>
#include <thread>
using namespace std;

int main(){
 chrono::system_clock::time_point  start, end; // 型は auto で可
 start = chrono::system_clock::now(); // 計測開始時間

 //Speep
 this_thread::sleep_for(chrono::milliseconds(100));

 end = chrono::system_clock::now();  // 計測終了時間
 int sec = chrono::duration_cast<chrono::seconds>(end-start).count(); 
 int msec = chrono::duration_cast<chrono::milliseconds>(end-start).count();
 int nsec = chrono::duration_cast<chrono::nanoseconds>(end-start).count();
 cout<<sec<<endl;
 cout<<msec<<endl;
 cout<<nsec<<endl;

}

ros::Timeに使い方が似ているので、いいですね。

 

 

vectorなどから、最小値、最大値、ソートなどを実施する

下記のように、

algorithmのヘッダと、

あたらしいbegin(),end()メソットを使うことで、

簡単にデータの最大値や最小値、ソートなどを簡単に実施することができます。

 

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main(){
  vector<double> data={0.1,-0.5,10.0,-20.2};

  //最大値と最小値を取得
  double max = *max_element( begin(data), end(data) );
  double min = *min_element( begin(data), end(data) );
  cout<<max<<","<<min<<endl;

  //最大値と最小値と同時に取得
  auto minmaxvalue=minmax_element(begin(data),end(data));
  cout<<*minmaxvalue.first<<","<<*minmaxvalue.second<<endl;

  //ソート
  sort(begin(data),end(data));
  for(auto i : data){
    cout<<i<<endl;
  }

}

 

ラムダ式による構造体データベースの抽出やソート、条件カウント

ラムダ式という、名前の無い関数オブジェクトを使うことで、

構造体を格納したコンテナのデータに対して、

条件を満たすデータの数を数えたり、

構造体の一つのデータに対してソートしたり、

条件を満たすデータのみを抽出することが

簡単にできます。

 

下記がサンプルコードです。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Person{
  string name;
  int age;
  bool isman;
};

int main(){
  cout<<"lamda sample"<<endl;

  vector<Person> plist;

  Person tom;
  tom.name="Tom";
  tom.age=19;
  tom.isman=true;

  Person sam;
  sam.name="Sam";
  sam.age=33;
  sam.isman=true;

  Person mie;
  mie.name="Mie";
  mie.age=31;
  mie.isman=false;

  plist.push_back(tom);
  plist.push_back(sam);
  plist.push_back(mie);

  //男の人を数える
  int mancount=count_if(begin(plist),end(plist),[](Person p){return p.isman;});
  cout<<"man count:"<<mancount<<endl;

  //20歳以上の人を数える
  int adultcount=count_if(begin(plist),end(plist),[](Person p){return p.age>=20;});
  cout<<"adult count:"<<adultcount<<endl;

  //年齢でソート
  sort(begin(plist),end(plist),[](Person a,Person b){return a.age<b.age;});
  cout<<"====Sort===="<<endl;
  for(auto p:plist){
    cout<<p.name<<":"<<p.age<<endl;
  }
 
  //20歳以上を抽出(20歳以下を削除)
  plist.erase(remove_if(begin(plist),end(plist),[](Person p){return p.age<20; }),end(plist));
  cout<<"====Erase===="<<endl;
  for(auto p:plist){
    cout<<p.name<<":"<<p.age<<endl;
  }
}

 

参考資料

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

MyEnigma Supporters

もしこの記事が参考になり、

ブログをサポートしたいと思われた方は、

こちらからよろしくお願いします。

myenigma.hatenablog.com