C++再学習メモ

個人的なC++の勉強し直しメモです。内容が充実してきたら記事再構成予定。

リンクメモ

ムーブセマンティクス(C++11から)

参考になるブログ

https://yohhoy.hatenablog.jp/entry/2012/12/15/120839
https://heavywatal.github.io/cxx/speed.html

  • C++ではy=xはコピーセマンティクスを実現するシンタックスである
  • auto_ptrはコピーシンタックスでムーブセマンティクスを実現していたため、わかりづらくて非推奨になっている
  • ムーブセマンティクスは本質的には以下と同じ
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <iostream>

    int main(void) {
    int* p = new int(123);
    int* q;
    q = p;
    p = nullptr;

    std::cout << *q << std::endl;

    return 0;
    }

Range-based for(C++11から)

auto eは値のコピーになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <vector>

int main(void) {
std::vector<int> a = {1, 2, 3};

for (auto& e : a) {
e *= 3;
}

for (const auto& e : a) {
std::cout << e << std::endl;
}
return 0;
}

for_each(C++03でも使える)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<vector>
#include<algorithm>
#include<iostream>

struct makeDouble {
void operator()(int& i) {
i *= 2;
}
};

void show(const int& v) {
std::cout << v << std::endl;
}

int main(void) {

std::vector<int> v = {1, 2, 3};

for_each(v.begin(), v.end(), makeDouble());

for_each(v.begin(), v.end(), show);

return 0;
}

std::transformってのもある。

Vector型のメモリ配置

  • メモリ領域は連続
  • 領域が足りなくなると別の箇所を再確保してコピーする
  • 使用サイズの目安がわかっているなら宣言時に要素数を確保しておくほうが無駄なコピーが発生しない
    1
    std::vector<int> v(100);  // 100が要素数

constexpr

コンパイル時定数。

Googleスタイルガイド

リンク

関数の入力用変数と出力用変数

前提として、出力用変数は可能な限り使わず、returnを用いるべきだが、
それでも出力用変数を使う場合。

  • 入力用変数は全て、出力用変数より先に位置する
  • 入力用変数はconst参照渡し
  • 出力用変数はポインタ渡しにしておくと区別がつきやすい
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

void calc_division(const int& dividend, const int& divisor, int* quotient, int* reminder) {
// intをconst参照渡しするのは本当は非推奨、ゼロ割とかはここでは気にしない
*quotient = dividend / divisor;
*reminder = dividend % divisor;
}

int main(void) {
int dividend = 30;
int divisor = 7;
int quotient = 0;
int reminder = 0;

// quotientとreminderは変更されるんだろうなと分かる
calc_division(dividend, divisor, &quotient, &reminder);

std::cout << "Quotient: " << quotient
<< ", Reminder: " << reminder << std::endl;

return 0;
}

Effective C++

宣言と定義

  • 宣言:コンパイラに名前と型を知らせるもの。
  • 関数のシグネチャ:仮引数の型の並び、戻り値の型
  • 定義:関数やメソッドに関して言えば、その本体を与えるもの
  • explicitなしで宣言されたコンストラクタは暗黙の型変換を行う。基本はexpicit付きにしておくと予想外の動作がない
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
26
27
28
29
30
31
#include <iostream>

class Foo {
public:
int x_;
Foo(int x): x_(x) {} // without explicit
};

class Hoge {
public:
int x_;
explicit Hoge(int x): x_(x) {} // with expicit
};

void test_foo(Foo f) {
std::cout << f.x_ << std::endl;
}

void test_hoge(Hoge h) {
std::cout << h.x_ << std::endl;
}


int main(void) {

test_foo(3); // implicit conversion: int -> Foo
// test_hoge(3); compile error!
test_hoge(Hoge(5)); // OK

return 0;
}
  • rhs: right hand side
  • オブジェクトの値渡しでコピーコンストラクタが呼ばれる
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# include <iostream>

using std::cout;
using std::endl;

class Test {
public:
Test() {
cout << "ctor: " << this << endl;
}

Test(const Test& rhs) {
cout << "copy ctor: " << this << ", rhs: " << &rhs << endl;
}

Test& operator=(const Test& rhs) {
cout << "copy asgn: " << this << ", rhs: " << &rhs << endl;
return *this;
}

~Test() {
cout << "dtor: " << this << endl;
}
};

void pass_by_value(Test t) {}

void pass_by_refference(const Test& t) {}

int main(void) {

cout << "1------------" << endl;
Test test;

cout << "2------------" << endl;
Test test2(test);

cout << "3------------" << endl;
Test test3;
test3 = test2;

cout << "4------------" << endl;
pass_by_value(test3);

cout << "5------------" << endl;
pass_by_refference(test3);

cout << "good bye" << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1------------                                                                                                                         
ctor: 0x7ffeb7660754
2------------
copy ctor: 0x7ffeb7660755, rhs: 0x7ffeb7660754
3------------
ctor: 0x7ffeb7660756
copy asgn: 0x7ffeb7660756, rhs: 0x7ffeb7660755
4------------
copy ctor: 0x7ffeb7660757, rhs: 0x7ffeb7660756
dtor: 0x7ffeb7660757
5------------
good bye
dtor: 0x7ffeb7660756
dtor: 0x7ffeb7660755
dtor: 0x7ffeb7660754
  • 定数文字列: const std::string Hello("hello");, const char* const Hello = "hello"

  • T* const: ポインタそのものが不変

  • const T*: ポインタが指し示すデータが不変
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

int main(void) {

char hoge[] = "hoge";
char foo[] = "foo";

const char* p = hoge; // data is const
char* const q = hoge; // pointer is const

p = foo;
// q = foo; compile error

std::cout << p << std::endl;

// p[2] = 'x'; compile error

return 0;
}
  • メンバ変数にmutableをつけるとビットレベルの不変性をもつメンバ関数からも変更できる
  • constなメンバ変数がある場合、コピー代入演算子は自動生成されない
  • コピーコンストラクタ、コピー代入演算子をprivateで宣言しておくとコピー、代入禁止にできる(定義はしない)。ただこれをやると値渡し、値返しもできないのでRVOを使った実装ができない。
  • 原則としてデストラクタで例外は投げない。例外が複数同時に発生しうる。
  • コンストラクタやデストラクタで仮想関数を呼ばない。ある派生クラスのコンストラクタを実行時、まず基底クラスのコンストラクタが呼ばれるがその中で仮想関数を呼んでいる場合、そこでよばれるのは派生クラスの関数ではない。
  • RAII(Resource Acquisition Is Initialization)

  • カプセル化するほど変更できる能力が増す

  • privateなデータにアクセスできる関数の数は少ないほうが良い
  • メンバ関数と、メンバ関数でない関数で同じことができるなら、後者のほうがよい(ここまで24項)