• Like
すごいConstたのしく使おう!
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

すごいConstたのしく使おう!

  • 5,395 views
Published

C++ 勉強会 in 筑波 での発表資料

C++ 勉強会 in 筑波 での発表資料

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
5,395
On SlideShare
0
From Embeds
0
Number of Embeds
8

Actions

Shares
Downloads
23
Comments
0
Likes
14

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. すごいconstたのしく使おう! const usage guide for C++ beginners @Regenschauer490 1
  • 2. はじめに 自己紹介  .sigure// (@Regenschauer490)  関西の大学院生  C++11歴8ヶ月の素人  const教穏健派 内容:  const の使い方を中心に、私が経験的に気づいたことを コード例と一緒に説明していきます。  右下に「ここまでする必要があるのか?」を表すレベルを 私個人で恣意的につけてみました。 ※ Lv 0 は C++を使う上で是非とも守るべきだと思うものです 2
  • 3. const 概要 cv修飾子の一種  const 修飾子 : 変数の値を変更できないよう指示  volatile 修飾子 : 最適化せずメモリへアクセスするよう指示 変数に対する修飾とメンバ関数に対する修飾がある const 修飾された変数の宣言時には初期化が必要 変数に対する修飾子の位置は 「const T」 「T const」 どちらでもOK 3
  • 4. 定数 定数は, #define の代わりに constを使う コンパイル時定数になるのは整数型だけ その他は実行時定数となる 正直、定数は constexpr を使いましょう 4 const int N = 5; //コンパイル時定数 int ar1[N] = { 1, 2, 3, 4, 5 }; // Nは無くても可 array<int, N> ar2{ { 1, 2, 3, 4, 5 } }; // initializer-list
  • 5. 定数 ユーザ定義型(クラス)のconst変数の宣言  暗黙のデフォルトコンストラクタだけではコンパイルエラー 自分で定義 or default指定 する必要がある 5 struct Hoge1{ string str_; }; struct Hoge2{ string str_; Hoge2() = default; }; Hoge1 hoge1; // OK Hoge1 const choge1; // error Hoge2 hoge2; // OK Hoge2 const choge2; // OK
  • 6. 関数の引数 T const& で引数をとる  引数元の内容が変更されないことを保証  Tがユーザ定義型(クラス)なら、値渡しより効率的  戻り値でローカル変数の参照(or ポインタ)を返すのはNG!  破棄されたローカル変数へアクセスしてしまう  moveで返そう  コンパイラが最適化してくれることもある(RVO/NRVO) 6 string AddGrass(string const& src){ auto tmp = src; // copy tmp.append(“w”); //appendは非constな操作 return tmp; // move(tmp) }
  • 7. 関数の引数 引数がプリミティブ型かユーザ定義型か分からない時  関数の引数がtemplate型の場合どうする?  boost::call_traits<T>::param_typeで最適な形へ変換  boost::call_traits<int>::param_type // int const  boost::call_traits<string>::param_type // string const&  ただし、関数templateには型推論の問題で使いづらい(下例)  クラスtemplateに対しては問題なく使える 7 template<typename T> void Foo(typename boost::call_traits<T>::param_type value){} Foo(1); // error : Tの型を推論できない Foo<int>(1); // OK
  • 8. メンバ関数 クラスの状態を変化させない(メンバ変数を書き換えない) メンバ関数は、constメンバ関数にしよう  constメンバ関数内では、メンバ変数は暗黙的にconst修飾される  const修飾された変数は、 constでないメンバ関数を呼び出せない  constメンバ関数内では、thisポインタもconst修飾される 8 class Hoge{ string hoge_; public: string Get () const{ hoge_ = ""; // error : string const hoge_.append(""); // error : append() const{…} SetString(""); // error : this->SetString(){…} return hoge_; } void Set (string const& src) { hoge_ = src; } };
  • 9. メンバ関数 constメンバ関数のオーバーロード  呼び出し元の変数(クラスオブジェクト)が・・・ const変数でない → 非constメンバ関数が呼び出される const変数である → constメンバ関数が呼び出される 9 class ConstCheck { public: ConstCheck (){} void Check (){ cout << "ないとき!" << endl; } void Check () const{ cout << "あるとき!" << endl; } }; ConstCheck ないとき; ConstCheck const あるとき; ないとき. Check(); //ないとき! あるとき. Check(); //あるとき!
  • 10. メンバ関数 mutable記憶クラス指定子  constメンバ関数内でも、メンバ変数がconst修飾されなくなる  主な使い道はクラス内部用キャッシュ 10 class Hoge{ mutable string hoge_; //よくない使い方 mutable vector<chrono::time_point<chrono::system_clock>> time_; public: string Get () const{ hoge_ = ""; // OK hoge_.append(""); // OK SetString(""); // error : this->SetString(){…} time_.push_back(chrono::system_clock::now()); // OK return hoge_; } void Set (string const& src) { hoge_ = src; } };
  • 11. constの連鎖性11 struct Hoge2{ string str_; Hoge2() = default; }; void Foo(Hoge2 const& v){ v.str_.append(“”); // error :v.str _はconst変数 }  以上から分かるように、constは連鎖する(重要)  const 変数・メンバ関数からは、アクセスするメンバ変数は const修飾され、非constメンバ関数は呼び出せない。  メンバ変数もconst修飾されていれば同様に制限がかかる。 オブジェクトの内側へどんどん伝搬していく  const_cast や mutable で部分的に破ることはできる
  • 12. constの連鎖性(代入)12 int a = 0; int const b = 0; int& ra = a; // OK int const& cra = a; // OK int& rb = b; // error int const& crb = b; // OK int d = cra; // copy: T const& -> T int const& e = []()->int{ return 1; }(); // T(pvalue) -> T const&  ポインタ・参照への代入時に、元の変数が・・・ const変数でない → ポインタ・参照のconst修飾は任意 const変数である → constポインタ・参照へのみ代入可  非ポインタ・参照への代入は コピーが行われる 別の変数が新たに作られるので、元の連鎖には関係しない
  • 13. ラムダ式 コピーキャプチャされた変数は暗黙的にconst修飾  参照キャプチャされた変数は元のまま mutableを付けるとコピーキャプチャでもconst修飾されない 13 ConstCheck hoge; [hoge]{ hoge.Check(); //あるとき! }(); [hoge]() mutable{ hoge.Check(); //ないとき! }(); [&hoge]{ hoge.Check(); //ないとき! }();
  • 14. ポインタ C++でポインタといえば shared_ptr 生ポインタ?なにそれ、おいしいの?  とりあえず、生ポインタのconstからおさらい 14 ptr自体がconstか ptrが指す先を const扱いするか T * const ptr T & ref ○ ☓ T const * ptr ☓ ○ T const * const ptr T const & ref ○ ○ ※ ついでだから参照も入れときました
  • 15. ポインタ 生ポインタと同じことがshared_ptrでもいえる 15  shared_ptr<T> const じゃ中身の値を変更できてしまう! ※私も以前にやらかしてしまいました (^^;)  typedef shared_ptr<string> StrPtr; とかしてるとやりがち  typedef shared_ptr<string const> C_StrPtr; も一緒に定義しよう ptr自体がconstか ptrが指す先を const扱いするか shared_ptr<T> const ptr ○ ☓ shared_ptr<T const> ptr ☓ ○ shared_ptr<T const> const ptr ○ ○
  • 16. ポインタ 実際にコンパイルして確認 16 ConstCheck a , b; ConstCheck * const p1 = &a; // p1自体がconst ConstCheck const * p2 = &b; // p2が指す先をconst扱い p1 = &b; // error p1->Check (); //ないとき! p2 = &a; p2->Check (); //あるとき! shared_ptr<ConstCheck> const s1(make_shared<ConstCheck>()); shared_ptr<ConstCheck const> s2(make_shared<ConstCheck>()); s1 = make_shared<ConstCheck>(); // error s1->Check (); //ないとき! s2 = make_shared<ConstCheck>(); s2->Check (); //あるとき!
  • 17. ポインタ ※ ポインタ・参照先が絶対に不変だとは保証しない  指している先の変数自体は非constかもしれない  constなポインタ・参照を通しての変更が不可というだけ 17 int a = 0; int const& cra = a; a = 1; cout << cra << endl; // 1 shared_ptr<int> s1(make_shared<int>(0)); shared_ptr<int const> s2(s1); *s1 = 1; cout << *s2 << endl; // 1 ポインタ(特にshared_ptr)を使う時は常に意識しておこう
  • 18. ポインタ const_cast によるcv修飾子の除去  constなポインタ・参照から、非constなポインタ・参照を得る  逆にconst を明示的に付与することもできる よくある使用例  古いライブラリの関数へ引数を渡す時  constメンバ関数の実装を非constメンバ関数で流用する時  operator[]の実装を、operator[]constと統一したい時など 18 void Print(char* str){ printf("%s", str); } string s = "example"; string const& crs = s; string& rs = const_cast<string&>(crs); rs = "change"; // OK Print( const_cast<char*>(crs.c_str()) );
  • 19. ポインタ ただし、正しく使わないと未定義動作を引き起こす  const_cast したい constポインタ(参照)の指す先が・・・ const変数でない → cast後に指す先を変更してもOK const変数である → cast後に指す先を変更すると爆死  そもそもconst_castを使う時点で設計ミス (とよく言われる) ※自分ではどうしようもない時以外、基本的に使わないでFA 19 int const b = 0; // 元の変数はconst int const& crb = b; int& rb = const_cast<int&>(crb); rb = 1; // Undefined Behavior
  • 20. ローカル変数 さて、constの重要さを十分理解していただけた所で、 次はローカル変数も可能な限りconstにしていきましょう まずはあまりイケてないコードを見てみましょう 20 enum class Cookie { CHOCOLATE, GOLDEN, RED }; auto cookie (Cookie::CHOCOLATE); string v1; //poor If(cookie = Cookie::RED) v1 = “apocalypsis“; // == のミスでコンパイルエラーにならない else v1 = "peace"; enum class Cookie { CHOCOLATE, GOLDEN, RED }; auto const cookie(Cookie::CHOCOLATE); auto const v1 = cookie == Cookie::RED ? "apocalypsis" : "peace"; 次にconstの恩恵を享受できるコードを見てみましょう
  • 21. ローカル変数 他にも同様に見ていきましょう 21 vector<Cookie> cookies{ Cookie::CHOCOLATE, Cookie::GOLDEN, Cookie::RED }; int sum = 0; // poor for(auto e : cookies){ sum += static_cast<int>(e); } vector<Cookie> const cookies{ Cookie::CHOCOLATE, Cookie::GOLDEN, Cookie::RED }; auto const sum = boost::accumulate(cookies, 0, [](int const sum, Cookie const ck){ return sum + static_cast<int>(ck); } ); ★const意識したらコードがきれいになりました (23歳 男性 大学院生)
  • 22. ローカル変数 やむを得ずconstでない変数を作成する場合は、 ラムダ式で非const変数の生存スコープを制限しよう 22 auto const file_pass = “…”; //読み込むファイル auto const data = [file_pass]{ vector<string> tmp; //後で追加するからconstにできない string line; ifstream ifs(file_pass); while (ifs && getline(ifs, line)) tmp.push_back(line); return move(tmp); }(); ローカル関数・スコープを作ることの副産物として、 処理の区切りが自然と生まれ、変更もしやすくなる
  • 23. ローカル変数23  ローカル変数をconstにする主なメリット  変数の使い回しを防ぎ、それぞれの変数の内容・役割を 明確にすることができる. 可読性もアップ↑↑  ローカル関数・スコープを使うようになる. 処理単位毎にコードがまとまり、変更に強い  forを使う機会が減り、STLのAlgorithmを使うようになる. コードが見やすくスッキリ
  • 24. 戻り値 メンバ変数の参照を渡したい場合  参照や shared_ptr を返す戻り値は const修飾しておこう shared_ptr 以外では、元のインスタンスの生存期間に注意  「メンバ変数を外部に晒すなんて・・・」 と一見思うかもしれ ないが、const修飾しておけば外部からの変更はできない。 const_cast という があるので、絶対ではない 24 class CCMaker{ shared_ptr<ConstCheck> cc_; public: //外部からは Read Only な操作しかできない shared_ptr<ConstCheck const> Get() const{ return cc_; } }; 不変性 コ ン ス ト 破 壊 ブレイカー
  • 25. 戻り値・引数 敬虔なconst教徒は, たとえ値返しでもconstを付ける  定数・不変ということを強調したい意図がある?  文法上は、あってもなくても同じ 敬虔なconst教徒は, たとえ値渡しでもconstを付ける  関数中で同じ変数を使いまわすことは悪である  ローカル変数と同じ思想 25 boost::optional<double const> const Div(double const num, double const denom) { return denom ? boost::optional<double const>(num / denom) : boost::none; }
  • 26. まとめ const 付けるとミスが減る! const 意識するとコードがスッキリする! const のおかげで なんだか幸せになれました などなど 縁の下の力持ち!な const さんと みなさんもこれから仲良くしていきましょう 26