Weitere ähnliche Inhalte Ähnlich wie C++ lecture-1 (20) C++ lecture-12. 1 コンストラクタ
前回最後に使ったコードを再掲します。
list 1 constructor0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# include <cstdio >
class hal_adc {
private :
unsigned int value ;
public :
unsigned int get_value () {
return this -> value;
}
void set_value ( unsigned int value) {
this -> value = value;
}
};
int main(int , char **) {
hal_adc adc0;
adc. set_value (3);
std :: printf ("%dn", adc. get_value ());
return 0;
}
5. list 2 constructor1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# include <cstdio >
class hal_adc {
private :
unsigned int value ;
public :
hal_adc () {
printf (" constructor called n");
}
};
int main(int , char **) {
hal_adc adc0; // constructor called
return 0;
}
このプログラムを実行してみればわかりますが、14 行目が
実行されると、hal adc::hal adc が呼び出されます。
この呼び出される関数をコンストラクタと呼びます。
14. list 6 constructor5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# include <cstdio >
struct data {
data(int dat) { std :: printf ("%d ", dat); }
};
struct hal_adc {
data dat0;
data dat1;
hal_adc () : dat0 (1) , dat1 (2) {};
hal_adc (int a) : dat1 (2) , dat0 (1) {};
};
int main(int , char **) {
hal_adc adc0;
// 1 2
std :: printf ("n");
hal_adc adc1 (1);
// 1 2
std :: printf ("n");
return 0;
}
また、new 演算子でインスタンスを作る場合もコンストラク
タが呼ばれます。
22. 4 デストラクタ
スコープを外れたり、delete されたりしたインスタンスは、
そのデストラクタが呼ばれ解体されます。
list 11 destructor0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# include <cstdio >
struct hal_adc {
hal_adc () {
std :: printf (" default constructor called n");
}
hal_adc ( const hal_adc &) {
std :: printf ("copy constructor called n");
}
˜ hal_adc () {
std :: printf (" destructor called n");
}
};
int main(int , char **) {
hal_adc adc; // constructor called
return 0;
// destructor called
}
24. 5 RAII
コンストラクタで new を使って領域を確保し、デストラクタ
で delete で領域を開放することで delete 忘れを防止でき
ます。
このようにコンストラクタで資源を確保し、デストラクタで
それを開放するというパターンを Resource Acquisition is
Initialization(RAII) と言います。
例外安全なプログラムを書くにはこの概念が不可欠です。
25. list 12 vector0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# include <cstdio >
# include <vector >
class raii_test
{
char *buf;
public :
rail_test ( raii_test &x) = delete ;
operator =() = delete ;
raii_test (int size) {
this ->buf = new char[size ];
}
˜ rail_test () {
delete [] this ->buf;
}
}
int main(int , char **) {
raii_test a(10);
// new
std :: scanf ("%s", a);
std :: printf ("%s", a);
}
// delete
27. 6 const
list 13 hal0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# include <stdio .h>
struct hal_adc {
unsigned int value ;
};
void set_adc_value ( hal_adc *adc , unsigned int value ) {
adc -> value = value ;
}
unsigned int get_adc_value (const hal_adc *adc) {
return adc -> value ;
}
int main(int argc , char ** argv)
{
hal_adc adc;
set_adc_value (&adc , 3);
printf ("%dn", get_adc_value (& adc));
return 0;
}
28. list 14 hal1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# include <cstdio >
class hal_adc {
private :
unsigned int value ;
public :
unsigned int get_value () {
return this -> value;
}
void set_value ( unsigned int value) {
this -> value = value;
}
};
int main(int argc , char ** argv)
{
hal_adc adc;
adc. set_value (3);
std :: printf ("%dn", adc. get_value ());
return 0;
}
この2つを比較してみると、get adc value には const とい
うキーワードがあるのに対し、hal adc::get value には
29. const というキーワードがありません。
list 15 hal2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# include <stdio .h>
struct hal_adc {
unsigned int value ;
};
void set_adc_value ( hal_adc *adc , unsigned int value ) {
adc -> value = value ;
}
unsigned int get_adc_value (const hal_adc *adc) {
return adc -> value ;
}
int main(int argc , char ** argv)
{
const hal_adc adc = {1};
printf ("%dn", get_adc_value (& adc));
return 0;
}
上のプログラムの 17 行目のようなことをするのには、下の
30. ように書く必要があります。
list 16 hal3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# include <cstdio >
class hal_adc {
private :
unsigned int value ;
public :
hal_adc (int vlaue ) : value (vlaue ) { }
unsigned int get_value () const {
return this -> value;
}
void set_value ( unsigned int value) {
this -> value = value;
}
};
int main(int argc , char ** argv)
{
const hal_adc adc;
std :: printf ("%dn", adc. get_value ());
return 0;
}
このように関数名のあとに const を書くと this ポインタの中
32. 7 演算子オーバーロード
例えば次のような複素数を実装したクラスを考えてみます。
list 17 operator0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class complex {
double re , im;
public :
complex ( double re , double im) {
this ->re = re;
this ->im = im;
}
complex add( complex &z) {
this ->re += z.re;
this ->im += z.im;
return *this;
}
};
int main(int , char **) {
complex x(1.0 , 2.1);
complex y(1.0 , 2.3);
x.add(y); // returns {2.0 , 4.4}
}
33. こういう実装もありえるでしょう。
list 18 operator1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class complex {
double re , im;
public :
complex ( double
this ->re =
this ->im =
}
friend complex
};
re , double im) {
re;
im;
add( complex &x, complex &z);
complex add( complex &x, complex &y) {
return complex (x.re + y.re , x.im + y.im); // calles constructor
}
int main(int , char **) {
complex x(1.0 , 2.1);
complex y(1.0 , 2.3);
add(x,y);
// returns {2.0 , 4.4}
}
でも使い勝手が悪いです。
36. list 20 operator3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class complex {
double re , im;
public :
complex ( double
this ->re =
this ->im =
}
friend complex
};
re , double im) {
re;
im;
operator +( complex &x, complex &z);
complex operator +( complex &x, complex &y) {
return complex (x.re + y.re , x.im + y.im); // calles constructor
}
int main(int , char **)
{
complex x(1.0 , 2.1);
complex y(1.0 , 2.3);
x+y; // returns {2.0 , 4.4}
}
C++ では演算子をオーバーロードすることはできますが、新
しい演算子を定義したり、結合性を変えたり、優先順位を変
40. 8 代入演算子
代入演算子はすでに初期化されている変数にインスタンスを
代入した時に呼ばれます。
list 21 assignment0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# include <cstdio >
struct hal_adc {
hal_adc () {
std :: printf (" default constructor called n");
}
hal_adc ( const hal_adc &) {
std :: printf ("copy constructor called n");
}
hal_adc & operator =( const hal_adc &x) {
std :: printf ("copy assignment operator called n");
return *this;
}
};
int main(int , char **)
{
hal_adc adc0;
// calles default constructor
43. 10 関数オブジェクト
operator() は先述したように引数の個数が特に決まっていま
せん。
list 23 function0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# include <iostream >
int succ0 (int i) { return i+1; }
struct succ1 {
int operator () (int i) { return i+1; }
};
int main(int , char **) {
int (*f0)(int) = succ0;
succ1 f1;
std :: cout << f0 (1) << std :: endl;
std :: cout << f1 (1) << std :: endl;
}
45. 11 型推論
以下のように auto というキーワードを使うことで右辺から
自動的に型が推論され、変数定義を楽に書けるようになり
ます。
1
2
3
4
auto x = 5;
const auto *v = &x, u = 6;
static auto y = 0.0;
auto int r;
//
//
//
//
OK: x has type
OK: v has type
OK: y has type
error : auto is
int
const int*, u has type const int
double
not a storage -class - specifier
Working Draft, Standard for Programming Language C++(N3337) p149 より引用
47. list 24 template0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# include <iostream >
# include <string >
using namespace std;
# define add(T) T add_ ##T(T x, T y){ return x+y;}
add(int);
add( double );
add( string );
int main(int , char **) {
cout << add_int (1, 2) << endl;
// int version
cout << add_double (1.0 , 2.0) << endl; // double version
cout << add_string ("a", "b") << endl; // string version
}
48. list 25 template1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# include <iostream >
# include <string >
using namespace std;
template < class T> T add(T x, T y) {
return x+y;
}
template int add <int >(int ,int);
template double add <double >( double , double );
template double add <string >( string , string );
int main(int , char **) {
cout << add <int >(1 , 2) << endl;
// int version
cout << add <double >(1.0 , 2.0) << endl; // double version
cout << add <string >("a", "b") << endl; // string version
}
49. list 26 template2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
# include <iostream >
# include <complex >
template < class T>
T add(T x, T y) {
return x+y;
}
int main(int , char **) {
std :: cout << add (1, 2) << std :: endl; // 版 int
std :: cout << add (1.0 , 2.0) << std :: endl; // 版 double
}
52. そこで関数の型として、同じ物を同じ型に封じ込められるよ
うにした便利なものとして、std::function があります。
list 28 function1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# include <iostream >
# include <functional >
int succ0 (int i) { return i+1; }
struct succ1 {
int operator () (int i) { return i+1; }
};
int main(int , char **) {
std :: function <int(int)> f0 = succ0;
std :: function <int(int)> f1 = succ1 ();
std :: cout << f0 (1) << std :: endl;
std :: cout << f1 (1) << std :: endl;
}
53. 14 ラムダ式
さて次のようなことがしたいとします。
list 29 addsth0.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# include <iostream >
class add_sth {
int x;
public :
add_sth (int _x) : {x=_x ;}
int operator () (int y) const {
return x+y;
}
};
int main(int , char **) {
int x = 5;
auto f = add_sth (x);
std :: cout << f(10) << std :: endl; // 15
}
55. ⟨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 ⟩
57. 最初の [] で囲われている部分が、lambda-introducer です。
ここにキャプチャしたい変数名を書きます。
最初の例では、コンストラクタで与えられた x をコピーして
保存していますがそれに当たります。
&を変数の前に付けないと、コピーによってキャプチャしま
す。キャプチャ元の変数が解体されたりしても問題ありま
せん。
&を変数の前につけるとコピーして保存するのでなく、参照
によってキャプチャするようになります。参照元が変更され
るとそれに従います。
[] 内のリストの先頭に [=] や [=, x] のように=を書いた場合は
ラムダ式内で使用した変数が暗黙的にコピーでキャプチャさ
58. れます。
[] 内のリストの先頭に [&] や [&, x] のように&を書いた場合は
ラムダ式内で使用した変数が暗黙的に参照でキャプチャされ
ます。
デフォルトではラムダ式の operator() は const で宣言されま
す。mutable をつけた場合、const が外れます。
次の () で囲われている部分が、
lambda-parameter-declaration です。普通の関数と同じよう
にパラメータを書きます。引数なしの関数オブジェクトを書
く場合はここを省略できます。
最後の {} で囲われている部分は compound-statement で、普
通の関数と書くべきことは同じです。
59. さてこのように書くと、どう表示されるでしょうか。
1
2
3
4
5
6
7
8
9
10
11
12
int a = 1, b = 1, c = 1;
auto m1 = [a, &b, &c]() mutable {
auto m2 = [a, b, &c]() mutable {
std :: cout << a << b << c;
a = 4; b = 4; c = 4;
};
a = 3; b = 3; c = 3;
m2 ();
};
a = 2; b = 2; c = 2;
m1 ();
std :: cout << a << b << c;
Working Draft, Standard for Programming Language C++(N3337) p91 より引用
60. 15 演習問題
• 次のような設計でオブジェクティブにコーティングしてみ
よう!
– main 関数は標準入力から文字列を受け取って一文字つ
づ、オブジェクト A に投げる。
– オブジェクト A は一文字つづ渡されるデータを集約す
る。改行が送られてくるまではデータをプールしておい
て、改行が来たらまとめて文字列としてオブジェクト B
に投げる。
– オブジェクト B は投げられた文字列を単純に表示する。
• std::vector と std::shared ptr の仕様を各自調べて、
practice::vector と practice::shared ptr を作ってみよう!
62. 17 Lisence
The text of this document is distributed under the Creative
Commons Attribution-ShareAlike 2.0 License.