c++ lecture-1

62
C++ 言語講習会第 1 回資料 書いた人:@sunaemon0(回路屋 2 )

Upload: sunaemon

Post on 24-Jan-2015

316 views

Category:

Documents


3 download

DESCRIPTION

 

TRANSCRIPT

Page 1: C++ lecture-1

C++言語講習会第1回資料

書いた人:@sunaemon0(回路屋2年)

Page 2: C++ lecture-1

1 コンストラクタ

前回最後に使ったコードを再掲します。

list 1 constructor0.cpp1 #include <cstdio>23 class hal_adc {4 private:5 unsigned int value;6 public:7 unsigned int get_value() {8 return this->value;9 }

10 void set_value(unsigned int value) {11 this->value = value;12 }

13 };

1415 int main(int, char **) {16 hal_adc adc0;

17 adc.set_value(3);

18 std::printf("%d\n", adc.get_value());

19 return 0;20 }

Page 3: C++ lecture-1

さて、cのコードの関数内で、1 struct a2 {

3 int i;4 };

56 struct a b;

みたいなことを書いたとします。

6行目の意味は、スタック領域に構造体a分の領域を確保するということです。

Page 4: C++ lecture-1

1 struct a2 {

3 int i;4 };

56 struct a b = {1};

と書けばこれは、スタック領域に構造体a分の領域を確保しその中の iを1で初期化するという意味になります。

Page 5: C++ lecture-1

list 2 constructor1.cpp1 #include <cstdio>23 class hal_adc {4 private:5 unsigned int value;6 public:7 hal_adc() {

8 printf("constructor called\n");

9 }

10 };

1112 int main(int, char **) {13 hal_adc adc0; // constructor called

14 return 0;15 }

このプログラムを実行してみればわかりますが、14行目が実行されると、hal adc::hal adcが呼び出されます。

この呼び出される関数をコンストラクタと呼びます。

Page 6: C++ lecture-1

つまり、C++で1 hal_adc adc0;

と書けば、スタック領域にhal adc分の領域を確保し、hal adcのコンストラクタにこの領域を初期化させるという意味になります。

Page 7: C++ lecture-1

2 デフォルトコンストラクタ

最初のコードでは、コンストラクタを定義しませんでしたが、コンパイルが通ります。これは、明示的にコンストラクタを書かなかった場合、コンパイラがデフォルトコンストラクタを暗黙的に生成するためです。

デフォルトコンストラクタとは引数を持たないコンストラクタ、または全ての引数にデフォルト引数が設定されているコンストラクタのことを言います。

コンパイル生成のデフォルトコンストラクタはこの場合何もしない関数になります。

この仕様によってCとの上位互換性が保たれています。

コンストラクタは引数を持つことができます。この場合は記

Page 8: C++ lecture-1

法が少し変わります。

list 3 constructor2.cpp1 #include <cstdio>23 class hal_adc {4 unsigned int value;5 public:6 unsigned int get_value() {7 return this->value;8 }

9 void set_value(unsigned int value) {10 this->value = value;11 }

12 hal_adc(unsigned int value) {13 this->value = value;14 }

15 hal_adc() {

16 this->value = value;17 }

18 };

1920 int main(int, char **) {21 hal_adc adc(3);

22 std::printf("%d\n", adc.get_value());

23 return 0;24 }

Page 9: C++ lecture-1

ユーザー定義のコンストラクタがあるためデフォルトコンストラクタは生成されないことに注意してください。

list 4 constructor3.cpp1 #include <cstdio>23 class hal_adc {4 unsigned int value;5 public:6 unsigned int get_value() {7 return this->value;8 }

9 void set_value(unsigned int value) {10 this->value = value;11 }

12 hal_adc(unsigned int value) {13 this->value = value;14 }

15 };

1617 int main(int, char **) {18 hal_adc adc; // no matching function for call to ‘hal_adc::hal_adc()’19 std::printf("%d\n", adc.get_value());

20 return 0;21 }

Page 10: C++ lecture-1

Cでは以下のように定義と初期化を同時に行なっても、分けて行なってもコストは変わりませんでした。

1 struct a b = {1};

1 struct a b;2 b.value = 1;

しかし、C++においては、一般的にはそうではありません。

定義と初期化を同時に行った場合、コンストラクタが一回呼ばれるだけです。

c++で後者のように書いた場合は、定義と初期化を行った後に、更に代入を行うという意味になります。

Page 11: C++ lecture-1

またコンストラクタには以下のように初期化リストを指定できます。

基本的には、メンバ変数全てについて初期化子を指定します。

list 5 constructor4.cpp1 #include <cstdio>23 struct data {4 data() { std::printf("default constructor called\n"); }

5 data(int) { std::printf("2nd constructor called\n"); }6 };

78 struct hal_adc {9 data dat;

10 hal_adc(int d) : dat(d) {};11 hal_adc() : dat() {};

12 };

1314 int main(int, char **) {15 hal_adc adc0; // default constructor called

16 hal_adc adc1(1); // 2nd constructor called

17 }

Page 12: C++ lecture-1

先ほどのように、初期化子を省略した場合、デフォルトコンストラクタが呼ばれます。もし初期化子を省略されたメンバ変数が、デフォルトコンストラクタを持たない場合、コンパイルエラーが起きます。

constなメンバ変数及び参照は初期化子によって初期化されなければそれ以降変更することは出来ません。

Page 13: C++ lecture-1

初期化子の呼ばれる順序は初期化リストの順序にではなく、メンバの宣言された順番であることに気をつけてください。

混乱を避けるため、初期化リストの順序はメンバの宣言された順序にあわせてください。

Page 14: C++ lecture-1

list 6 constructor5.cpp1 #include <cstdio>23 struct data {4 data(int dat) { std::printf("%d ", dat); }5 };

67 struct hal_adc {8 data dat0;

9 data dat1;

10 hal_adc() : dat0(1), dat1(2) {};

11 hal_adc(int a) : dat1(2), dat0(1) {};12 };

1314 int main(int, char **) {15 hal_adc adc0; // 1 2

16 std::printf("\n");

17 hal_adc adc1(1); // 1 2

18 std::printf("\n");

19 return 0;20 }

また、new演算子でインスタンスを作る場合もコンストラクタが呼ばれます。

Page 15: C++ lecture-1

別にコンストラクタを定義しない場合に、暗黙的にデフォルトコンストラクタが作られてほしくないい場合、=deleteを後につけます。

list 7 constructor6.cpp1 #include <cstdio>23 struct hal_adc {4 hal_adc() = delete;5 };

67 int main(int argc, char **argv)8 {

9 hal_adc adc0; // use of deleted function

10 return 0;11 }

デフォルトコンストラクタを明示的にprivateで宣言するという方法もありますが、こちらのほうが意図がわかりやすいのでおすすめします。

Page 16: C++ lecture-1

別にコンストラクタを定義する場合に、デフォルトコンストラクタをコンパイラ生成させたい場合、=defalutを後につけます。

list 8 constructor7.cpp1 #include <cstdio>23 struct hal_adc {4 hal_adc() = default;5 hal_adc(int a);6 };

78 int main(int argc, char **argv)9 {

10 hal_adc adc0; // ok

11 return 0;12 }

ここでは詳しく述べませんが、コンパイラが生成したデフォルトコンストラクタと、それに等価な明示的に書いたデフォ

Page 17: C++ lecture-1

ルトコンストラクタはC++の文法上扱いが異なるためこのような措置があります。

Page 18: C++ lecture-1

3 コピーコンストラクタ

引数として自分自身のインスタンスを取るコンストラクタのことをコピーコンストラクタと呼びます。

正確には、class Tのコンストラクタであって、引数として&T, const &T, volatile &T, volatile const &T型の変数を取るものをいいます。

参照であることに注意してください。

コピーコンストラクタはその名の通りインスタンスをコピーして新しいコンストラクタを作る必要があるときに呼び出されます。

Page 19: C++ lecture-1

list 9 copyconstructor0.cpp1 #include <cstdio>23 struct hal_adc {4 hal_adc() = default;5 hal_adc(const hal_adc&) {6 std::printf("copy constructor called\n");

7 }

8 };

910 int main(int, char **)11 {

12 hal_adc adc0;

13 hal_adc adc1 = adc0; // copy constructor called

14 return 0;15 }

Page 20: C++ lecture-1

先ほどと同じようにユーザー定義のコピーコンストラクタを定義しない場合、コンパイラはにコピーコンストラクタを生成します。

仕様上インスタンスをコピー出来ないようにしたいクラスではコピーコンストラクタを削除することを忘れないようにしてください。

Page 21: C++ lecture-1

引数を値渡しする場合にもコピーコンストラクタが呼ばれます。

list 10 copyconstructor1.cpp1 #include <cstdio>23 struct hal_adc {4 hal_adc() = default;5 hal_adc(const hal_adc&) {6 std::printf("copy constructor called\n");

7 }

8 };

910 void f(const hal_adc&) { }11 void g(hal_adc) { }1213 int main(int, char **)14 {

15 hal_adc adc;

16 f(adc); // no constructor called

17 g(adc); // constructor not called

18 return 0;19 }

Page 22: C++ lecture-1

4 デストラクタ

スコープを外れたり、deleteされたりしたインスタンスは、そのデストラクタが呼ばれ解体されます。

list 11 destructor0.cpp1 #include <cstdio>23 struct hal_adc {4 hal_adc() {

5 std::printf("default constructor called\n");

6 }

7 hal_adc(const hal_adc&) {8 std::printf("copy constructor called\n");

9 }

10 ˜hal_adc() {

11 std::printf("destructor called\n");

12 }

13 };

1415 int main(int, char **) {16 hal_adc adc; // constructor called

17 return 0; // destructor called

18 }

Page 23: C++ lecture-1

静的領域に確保されたインスタンスは、main関数が呼ばれる前にコンストラクタで構築され、mainを抜けた後にデストラクタによって解体されます。

デストラクタそのものはそんなに難しくはないのですが、例外や継承と組み合わさると注意が必要になってきます。

Page 24: C++ lecture-1

5 RAIIコンストラクタでnewを使って領域を確保し、デストラクタでdeleteで領域を開放することでdelete忘れを防止できます。

このようにコンストラクタで資源を確保し、デストラクタでそれを開放するというパターンをResource Acquisition isInitialization(RAII)と言います。

例外安全なプログラムを書くにはこの概念が不可欠です。

Page 25: C++ lecture-1

list 12 vector0.cpp1 #include <cstdio>2 #include <vector>34 class raii_test5 {

6 char *buf;78 public:9 rail_test(raii_test &x) = delete;

10 operator=() = delete;1112 raii_test(int size) {13 this->buf = new char[size];14 }

15 ˜rail_test() {

16 delete[] this->buf;17 }

18 }

1920 int main(int, char **) {21 raii_test a(10); // new

2223 std::scanf("%s", a);

24 std::printf("%s", a);

25 } // delete

Page 26: C++ lecture-1

標準ライブラリでは、std::vector,std::unique ptr,shd::shared ptr(メモリ資源をRAIIで管理)やstd::fstream(ファイルポインタをRAIIで管理)やstd::lock guard(セマフォとRAIIで管理)などが例として挙げられます。

Page 27: C++ lecture-1

6 const

list 13 hal0.cpp1 #include <stdio.h>23 struct hal_adc {4 unsigned int value;5 };

67 void set_adc_value(hal_adc *adc, unsigned int value) {8 adc->value = value;

9 }

1011 unsigned int get_adc_value(const hal_adc *adc) {12 return adc->value;13 }

1415 int main(int argc, char **argv)16 {

17 hal_adc adc;

18 set_adc_value(&adc, 3);

19 printf("%d\n", get_adc_value(&adc));

20 return 0;21 }

Page 28: C++ lecture-1

list 14 hal1.cpp1 #include <cstdio>23 class hal_adc {4 private:5 unsigned int value;6 public:7 unsigned int get_value() {8 return this->value;9 }

10 void set_value(unsigned int value) {11 this->value = value;12 }

13 };

1415 int main(int argc, char **argv)16 {

17 hal_adc adc;

18 adc.set_value(3);

19 std::printf("%d\n", adc.get_value());

20 return 0;21 }

この2つを比較してみると、get adc valueにはconstというキーワードがあるのに対し、hal adc::get valueには

Page 29: C++ lecture-1

constというキーワードがありません。

list 15 hal2.cpp1 #include <stdio.h>23 struct hal_adc {4 unsigned int value;5 };

67 void set_adc_value(hal_adc *adc, unsigned int value) {8 adc->value = value;

9 }

1011 unsigned int get_adc_value(const hal_adc *adc) {12 return adc->value;13 }

1415 int main(int argc, char **argv)16 {

17 const hal_adc adc = {1};18 printf("%d\n", get_adc_value(&adc));

19 return 0;20 }

上のプログラムの17行目のようなことをするのには、下の

Page 30: C++ lecture-1

ように書く必要があります。

list 16 hal3.cpp1 #include <cstdio>23 class hal_adc {4 private:5 unsigned int value;6 public:7 hal_adc(int vlaue) : value(vlaue) { }8 unsigned int get_value() const {9 return this->value;

10 }

11 void set_value(unsigned int value) {12 this->value = value;13 }

14 };

1516 int main(int argc, char **argv)17 {

18 const hal_adc adc;19 std::printf("%d\n", adc.get_value());

20 return 0;21 }

このように関数名のあとにconstを書くと thisポインタの中

Page 31: C++ lecture-1

身を変更できなくなります。

また、constのインスタンスの非constなメンバ関数は呼べません。

Page 32: C++ lecture-1

7 演算子オーバーロード

例えば次のような複素数を実装したクラスを考えてみます。

list 17 operator0.cpp1 class complex {2 double re, im;3 public:4 complex(double re, double im) {5 this->re = re;6 this->im = im;7 }

8 complex add(complex &z) {

9 this->re += z.re;10 this->im += z.im;11 return *this;12 }

13 };

1415 int main(int, char **) {16 complex x(1.0, 2.1);

17 complex y(1.0, 2.3);

18 x.add(y); // returns {2.0, 4.4}

19 }

Page 33: C++ lecture-1

こういう実装もありえるでしょう。

list 18 operator1.cpp1 class complex {2 double re, im;3 public:4 complex(double re, double im) {5 this->re = re;6 this->im = im;7 }

8 friend complex add(complex &x, complex &z);9 };

1011 complex add(complex &x, complex &y) {

12 return complex(x.re + y.re, x.im + y.im); // calles constructor13 }

1415 int main(int, char **) {16 complex x(1.0, 2.1);

17 complex y(1.0, 2.3);

1819 add(x,y); // returns {2.0, 4.4}

20 }

でも使い勝手が悪いです。

Page 34: C++ lecture-1

特に複数の数字を足すときに見た目が煩雑になります。

そこで、C++ではadd(x,y)やx.add(y)の代わりにx+yとかけるようにする仕組みがあります。これを演算子オーバーロードと言います。

具体的には以下のように書きます。

Page 35: C++ lecture-1

list 19 operator2.cpp1 class complex {2 double re, im;3 public:4 complex(double re, double im) {5 this->re = re;6 this->im = im;7 }

8 complex operator+(complex &z) {9 this->re += z.re;

10 this->im += z.im;11 return *this;12 }

13 };

1415 int main(int, char **) {16 complex x(1.0, 2.1);

17 complex y(1.0, 2.3);

1819 x+y; // returns {2.0, 4.4}

20 }

Page 36: C++ lecture-1

list 20 operator3.cpp1 class complex {2 double re, im;3 public:4 complex(double re, double im) {5 this->re = re;6 this->im = im;7 }

8 friend complex operator+(complex &x, complex &z);9 };

1011 complex operator+(complex &x, complex &y) {12 return complex(x.re + y.re, x.im + y.im); // calles constructor13 }

1415 int main(int, char **)16 {

17 complex x(1.0, 2.1);

18 complex y(1.0, 2.3);

1920 x+y; // returns {2.0, 4.4}

21 }

C++では演算子をオーバーロードすることはできますが、新しい演算子を定義したり、結合性を変えたり、優先順位を変

Page 37: C++ lecture-1

えたりすることはできません。

Page 38: C++ lecture-1

C++ではほとんどの単項演算子と、二項演算子がオーバーロードできます。オーバーロード出来ないのは三項演算子?:とスコープ解決子::くらいです。

ただし、++,–,(),[],->もオーバーロードできますが、後述の通り、オーバーロード方法が少し他とは異なります。

普通の演算子については、もしクラスTで、ある単項演算子@がオーバーロードされていれば、@ xというのは、operator@(x)ないし、x.operator@()と等価になります。

また、もしクラスTで、ある単項演算子@がオーバーロードされていれば、x @ yというのは、operator@(x, y)ないし、x.operator@(y)と等価になります。

Page 39: C++ lecture-1

++及び–は前置・後置の区別があり、普通引数を変更するので扱いが少し異なりますがここでは省略します。

=,(),[],->は非メンバ関数としてオーバーロードすることはできません。

operator=は、代入演算子とよばれます。他の演算子は継承されるのに対し、代入演算子は継承されません。また引数の型がT, T&, const T&, volatile T&, or const volatile T&のうちどれかである者は特にコピー代入演算子と呼ばれます。

operator()は、他と違っていくつの引数でも取れます。

operator->()は特殊ですが細かいことは省略します。

Page 40: C++ lecture-1

8 代入演算子

代入演算子はすでに初期化されている変数にインスタンスを代入した時に呼ばれます。

list 21 assignment0.cpp1 #include <cstdio>23 struct hal_adc {4 hal_adc() {

5 std::printf("default constructor called\n");

6 }

7 hal_adc(const hal_adc&) {8 std::printf("copy constructor called\n");

9 }

10 hal_adc &operator=(const hal_adc &x) {11 std::printf("copy assignment operator called\n");

12 return *this;13 }

14 };

1516 int main(int, char **)17 {

18 hal_adc adc0; // calles default constructor

Page 41: C++ lecture-1

19 hal_adc adc1 = adc0; // calles copy constructor

20 adc0 = adc1; // calles assignment operator

21 return 0;22 }

Page 42: C++ lecture-1

9 iostreamoperator¡¡のオーバーロードを乱用して、C++ではこんなことができるようになりました。オーバーロードによって%dみたいに型を明示しなくても標準出力に吐き出せるようになりました。

list 22 iostream0.cpp1 #include <string>2 #include <iostream >34 int main(int,char**)5 {

6 std::string s;

7 std::cin >> s;

8 std::cout << s << std::endl;

9 std::cerr << "no error << std::endl"

10 }

Page 43: C++ lecture-1

10 関数オブジェクト

operator()は先述したように引数の個数が特に決まっていません。

list 23 function0.cpp1 #include <iostream >23 int succ0(int i) { return i+1; }45 struct succ1 {6 int operator() (int i) { return i+1; }7 };

89

10 int main(int, char **) {11 int (*f0)(int) = succ0;12 succ1 f1;

1314 std::cout << f0(1) << std::endl;

15 std::cout << f1(1) << std::endl;

16 }

Page 44: C++ lecture-1

このようにoperator()を使うとまるでまるでそのクラスのインスタンスが関数のように振る舞います。

このように関数のように振る舞うオブジェクトを関数オブジェクトといいます。

Page 45: C++ lecture-1

11 型推論

以下のようにautoというキーワードを使うことで右辺から自動的に型が推論され、変数定義を楽に書けるようになります。

1 auto x = 5; // OK: x has type int

2 const auto *v = &x, u = 6; // OK: v has type const int*, u has type const int

3 static auto y = 0.0; // OK: y has type double

4 auto int r; // error: auto is not a storage-class-specifier

Working Draft, Standard for Programming Language C++(N3337) p149より引用

Page 46: C++ lecture-1

12 テンプレート

本格的には次回以降やりますが、C++にはテンプレートという便利な奴があります。

マクロを型安全にしたものであると言えます。

Page 47: C++ lecture-1

list 24 template0.cpp1 #include <iostream >2 #include <string>34 using namespace std;56 #define add(T) T add_##T(T x, T y){return x+y;}78 add(int);9 add(double);

10 add(string);

1112 int main(int, char **) {13 cout << add_int(1, 2) << endl; //int version

14 cout << add_double(1.0, 2.0) << endl; //double version

15 cout << add_string("a", "b") << endl; //string version

16 }

Page 48: C++ lecture-1

list 25 template1.cpp1 #include <iostream >2 #include <string>34 using namespace std;56 template <class T> T add(T x, T y) {7 return x+y;8 }

910 template int add<int>(int,int);11 template double add<double >(double,double);12 template double add<string >(string,string);1314 int main(int, char **) {15 cout << add<int>(1, 2) << endl; // int version

16 cout << add<double >(1.0, 2.0) << endl; // double version17 cout << add<string >("a", "b") << endl; // string version

18 }

Page 49: C++ lecture-1

list 26 template2.cpp1 #include <iostream >2 #include <complex>34 template <class T>5 T add(T x, T y) {

6 return x+y;7 }

89 int main(int, char **) {

10 std::cout << add(1, 2) << std::endl; //版int11 std::cout << add(1.0, 2.0) << std::endl; //版double12 }

Page 50: C++ lecture-1

13 関数オブジェクトと関数ポインタ

C++には、その後に ()をつけて呼び出せるものが三種類あります。

• 関数ポインタ• 関数オブジェクト• メンバ関数ポインタ (今回の講習会ではやらないかも)

Page 51: C++ lecture-1

関数ポインタと関数オブジェクトについてはどちらも ()を付けるだけで同じように呼び出すことができます。

しかし、変数に保存しようとしたり、戻り値として返そうとしたときには別の型になってしまいます。

list 27 function0.cpp1 #include <iostream >23 int succ0(int i) { return i+1; }45 struct succ1 {6 int operator() (int i) { return i+1; }7 };

89

10 int main(int, char **) {11 int (*f0)(int) = succ0;12 succ1 f1;

1314 std::cout << f0(1) << std::endl;

15 std::cout << f1(1) << std::endl;

16 }

Page 52: C++ lecture-1

そこで関数の型として、同じ物を同じ型に封じ込められるようにした便利なものとして、std::functionがあります。

list 28 function1.cpp1 #include <iostream >2 #include <functional >34 int succ0(int i) { return i+1; }56 struct succ1 {7 int operator() (int i) { return i+1; }8 };

910 int main(int, char **) {11 std::function <int(int)> f0 = succ0;12 std::function <int(int)> f1 = succ1();1314 std::cout << f0(1) << std::endl;

15 std::cout << f1(1) << std::endl;

16 }

Page 53: C++ lecture-1

14 ラムダ式

さて次のようなことがしたいとします。

list 29 addsth0.cpp1 #include <iostream >23 class add_sth {4 int x;5 public:6 add_sth(int _x) : {x=_x;}78 int operator() (int y) const {9 return x+y;

10 }

11 };

1213 int main(int, char **) {14 int x = 5;1516 auto f = add_sth(x);1718 std::cout << f(10) << std::endl; // 15

19 }

Page 54: C++ lecture-1

これをもっと簡単に書く方法として、ラムダ式があります。

list 30 addsth1.cpp1 #include <iostream >23 int main(int, char **) {4 int x = 5;56 auto f = [x](int y){ return x+y; };78 std::cout << f(10) << std::endl; // 15

9 }

見慣れない式がありますが、これがラムダ式です。

ラムダ式の文法を解説します。BNFで書くと以下のとおりです。

Page 55: C++ lecture-1

⟨lambda-expression⟩ ::= ⟨lambda-introducer⟩ [⟨lambda-parameter-declaration⟩]⟨compound-statement⟩

⟨lambda-introducer⟩ ::= ‘[’ [⟨lambda-capture⟩] ‘]’

⟨lambda-capture⟩ ::= ⟨capture-default⟩ | ⟨capture-list⟩ | ⟨capture-default⟩ ‘,’⟨capture-list⟩

⟨capture-default⟩ ::= ‘&’| ‘=’

⟨capture-list⟩ ::= ⟨capture⟩ ‘|’ ⟨capture-list⟩ , ⟨capture⟩

⟨capture⟩ ::= ⟨identifier⟩ | ‘&’ identifier | ‘this’

⟨lambda-parameter-declaration⟩ ::= ‘(’ [⟨lambda-parameter-declaration-list⟩] ‘)’[⟨mutable-specification⟩] [⟨exception-specification⟩][⟨lambda-return-type-clause⟩]

⟨lambda-parameter-declaration-list⟩ ::= ⟨lambda-parameter⟩ | ⟨lambda-parameter⟩‘,’ ⟨lambda-parameter-declaration-list⟩

⟨lambda-parameter⟩ ::= ⟨decl-specifier-seq⟩ ⟨declarator⟩

Page 56: C++ lecture-1

⟨lambda-return-type-clause⟩ ::= ‘->’ ⟨type-id⟩

Working Draft, Standard for Programming Language C++(N3337) p88を基に作成

Page 57: C++ lecture-1

最初の []で囲われている部分が、lambda-introducerです。ここにキャプチャしたい変数名を書きます。

最初の例では、コンストラクタで与えられたxをコピーして保存していますがそれに当たります。

&を変数の前に付けないと、コピーによってキャプチャします。キャプチャ元の変数が解体されたりしても問題ありません。

&を変数の前につけるとコピーして保存するのでなく、参照によってキャプチャするようになります。参照元が変更されるとそれに従います。

[]内のリストの先頭に [=]や [=, x]のように=を書いた場合はラムダ式内で使用した変数が暗黙的にコピーでキャプチャさ

Page 58: C++ lecture-1

れます。

[]内のリストの先頭に [&]や [&, x]のように&を書いた場合はラムダ式内で使用した変数が暗黙的に参照でキャプチャされます。

デフォルトではラムダ式のoperator()はconstで宣言されます。mutableをつけた場合、constが外れます。

次の ()で囲われている部分が、lambda-parameter-declarationです。普通の関数と同じようにパラメータを書きます。引数なしの関数オブジェクトを書く場合はここを省略できます。

最後の{}で囲われている部分はcompound-statementで、普通の関数と書くべきことは同じです。

Page 59: C++ lecture-1

さてこのように書くと、どう表示されるでしょうか。1 int a = 1, b = 1, c = 1;2 auto m1 = [a, &b, &c]() mutable {3 auto m2 = [a, b, &c]() mutable {4 std::cout << a << b << c;

5 a = 4; b = 4; c = 4;

6 };

7 a = 3; b = 3; c = 3;

8 m2();

9 };

10 a = 2; b = 2; c = 2;

11 m1();

12 std::cout << a << b << c;

Working Draft, Standard for Programming Language C++(N3337) p91より引用

Page 60: C++ lecture-1

15 演習問題

• 次のような設計でオブジェクティブにコーティングしてみよう!– main関数は標準入力から文字列を受け取って一文字つづ、オブジェクトAに投げる。

– オブジェクトAは一文字つづ渡されるデータを集約する。改行が送られてくるまではデータをプールしておいて、改行が来たらまとめて文字列としてオブジェクトBに投げる。

– オブジェクトBは投げられた文字列を単純に表示する。• std::vectorとstd::shared ptrの仕様を各自調べて、

practice::vectorとpractice::shared ptrを作ってみよう!

Page 61: C++ lecture-1

16 Referencehttp://www.cplusplus.com/

http://en.cppreference.com/w/

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

Page 62: C++ lecture-1

17 LisenceThe text of this document is distributed under the CreativeCommons Attribution-ShareAlike 2.0 License.