topazの遊び場

いろいろやってみる

そろそろC++やるぞパート24 演算子 「sizeof」「後置++, --」

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は「sizeof演算子」と「後置インクリメント/デクリメント」を行っていきます。

sizeof 演算子 sizeof

構文

sizeof unary-expression
sizeof  ( type-name )

charのサイズに対する相対的になサイズが返されます。charのサイズは基本的に1となっています。

演算結果としてsize_tが返されます。

int i = 100;
size_t si = sizeof(i);
cout << si << endl;

long l = 100;
size_t sl = sizeof(l);
cout << sl << endl;

Output

4
8

sizeofを重ねて、こういうこともできます。

int i = 100;
size_t si = sizeof(i);
size_t s = sizeof(sizeof(sizeof(sizeof(sizeof(si)))));

配列の要素数の取得

sizeof array / sizeof array[0]

sizeof array は配列に使用しているメモリ全て、 sizeof array[0] は配列の型の大きさを表します。

int ar[2] = {13, 17};
size_t arSize = sizeof(ar) / sizeof(ar[0]);
cout << arSize << endl;

Output

2

前置インクリメント/デクリメント演算子: ++. --

構文

++ unary-expression
-- unary-expression

後置インクリメント/デクリメントと同様に、1単位分増減させます。

int i = 100;
cout << i << endl;
++i;
cout << i << endl;
--i;
cout << i << endl;

Output

100
101
100

前置と後置の違い

同じ「1単位分増減させる」演算子ですが、それぞれ定義されていることには理由があります。それが、評価と使用の順番です。それぞれこのようになっています。

前置 インクリメント/デクリメント

  1. 評価
  2. 使用

後置インクリメント/デクリメント

  1. 使用
  2. 評価

何を意味しているのかソースコードで見てみます。

int i = 100;
int j = i++;
cout << i << endl;
cout << j << endl;

int i2 = 100;
int j2 = ++i2;
cout << i2 << endl;
cout << j2 << endl;

Output

101
100
101 
101

int j = i++;のところは先にint j = i;として代入されて、使用された後にi++が評価されて101に変わります。
一方で、int j2 = ++i2;は先に++i2が評価されて101になって、int j2 = 101;となり、j2は101になります

アセンブリ

前置と後置でコンパイルした結果を見てみます。以下の2つのソースコードで試します。

main.cpp

#include <iostream>
using namespace std;

int main()
{
    int i = 100;
    int j = i++;
}

main2.cpp

#include <iostream>
using namespace std;

int main()
{
    int i = 100;
    int j = ++i;
}

コンパイルには-Sをつけてアセンブリに、ファイルの比較にはdiffコマンドを使用します。

$ diff main.cpp main2.cpp 
7c7
<     int j = i++;
---
>     int j = ++i;
$ g++ -S main.cpp -o a.s 
$ g++ -S main2.cpp -o b.s
$ diff a.s b.s           
13,14c13,14
<       add     w9, w8, #1
<       str     w9, [sp, #12]
---
>       add     w8, w8, #1
>       str     w8, [sp, #12]

アセンブリにした時に差分は2行現れました。これだけでは分かりにくいので、前後を少し取り出してみます。

a.s

mov  w8, #100
str w8, [sp, #12]
ldr w8, [sp, #12]
add w9, w8, #1
str w9, [sp, #12]
str w8, [sp, #8]
  1. 100を w8 レジスタに入れる
  2. w8レジスタの内容を #12 に保存
  3. #12の内容を w8 レジスタに読み込み
  4. w8 と 1 を足した値をw9に入れる
  5. w9レジスタの内容を #12 に保存
  6. w8レジスタの内容を #8 に保存

b.s

mov  w8, #100
str w8, [sp, #12]
ldr w8, [sp, #12]
add w8, w8, #1
str w8, [sp, #12]
str w8, [sp, #8]
  1. 100を w8 レジスタに入れる
  2. w8レジスタの内容を #12 に保存
  3. #12の内容を w8 レジスタに読み込み
  4. w8 と 1 を足した値をw8に入れる
  5. w8レジスタの内容を #12 に保存
  6. #8の内容をw8レジスタに読み込み。

後置インクリメントの場合は、w9レジスタに値に計算した値を一度入れています。#12にはインクリメントされた101、#8にはインクリメント前の100が入っている状態になります。

このように少し違いがあるので使用する際には注意が必要です。

参考リスト

感想

インクリメントとデクリメントが間違えずに使わないとエラーが出そうです。sizeof演算子はあまり使わないと思います。配列の要素数もこれを用いて求めるよりは初期化した値を取っておく方がよいかなと思いました。

次回も引き続き演算子を行おうと思います。

そろそろC++やるぞパート23 演算子 「キャスト演算子」

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は「4種類のキャスト演算子」を行っていきます。

const_cast 演算子

構文

const_cast <type-id> (expression)

const_castは型変換をするのでなく、constvolatileの追加や削除を行います。

const int *a = new int(13);
int *b = const_cast<int *>(a);
volatile int *c = new int(123);
int *d = const_cast<int *>(c);

継承された型について

class Base
{
};

class Derived : public Base
{
};

const_castの型指定に親の型を書いてキャストすることはできません。キャストしてから親の型の変数に代入します。

const Derived *a = new Derived{};
Derived *b1 = const_cast<Derived *>(a);    // OK
Base *b2 = const_cast<Derived *>(a);    // OK
Base *b3 = const_cast<Base *>(a);    // Error

これは、あまり使わないと思います。

dynamic_cast 演算子

構文

dynamic_cast < type-id > ( expression )

小クラスから親クラスへキャストする時に使用します。正確にはポリモーフィッククラスにキャスト可能だそうです。

Derived *a = new Derived();
Derived *b = dynamic_cast<Derived *>(a);
Base *c = dynamic_cast<Base *>(a);

Derived *d = dynamic_cast<Derived *>(c);    //  Error

これは、使いそうです。

reinterpret_cast 演算子

構文

reinterpret_cast < type-id > ( expression )

他のポイント型に、強制的に型変換します。

charintlongなど実質整数型の時に相性が良く使えます。

char *e = new char('a');
int *f = reinterpret_cast<int *>(e);    //  OK

これは必要に応じて使いそうです。

static_cast 演算子

構文

static_cast <type-id> ( expression )

暗黙の型変換を明示的に行う時に使います。省略できる場合がほとんどです。

long a = 100;
int b = a;
int c = static_cast<int>(a);

あまり明示的にしたい時はないような気がしますが、必要になったら使います。

参考リスト

感想

今回も演算子の続きを行いました。ポインタのキャストや、明示的なキャストと4種類やりましたが、dynamic_castをよく使うのかなという印象です。

次回も、演算子の続きを行なっていきます。

そろそろC++やるぞパート22 演算子 「( )」「++」「--」「[typeid」

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は演算子( ),++,--,typeidを行っていきます。

関数呼び出し演算子 ()

構文

postfix-expression:
 postfix-expression ( argument-expression-list opt )

引数は、式のコンマ区切りの一覧であるargument-expression-listからなります。argument-expression-listは空の場合もあります。

引数式は、不定の順序で評価されます。

とは書いてありますが、こちらのコードで100回ループしても引数順に呼び出されました。

int Write(string text, int val)
{
    cout << text << endl;
    return val;
}

void ReceiveFunc(int a, int b, int c)
{
    cout << a << ":" << b << ":" << c << endl;
}

int main()
{
    for (int i = 0; i < 100; i++)
    {
        ReceiveFunc(Write("one", 1), Write("two", 2), Write("three", 3));
    }
}
  • 値を返す関数
int AAA()
{
    int a = 123;
    return a;
}
  • ポインタを返す関数
int *BBB()
{
    int *b = new int(123);
    return b;
}
  • 参照を返す関数
int &CCC()
{
    int *c = new int(123);
    int &d = *c;
    return d;
}

後置 インクリメント/デクリメント 演算子 ++, --

構文

postfix-expression ++
postfix-expression --

C++には前置インクリメントと後置インクリメントが共に実装されています。

後置インクリメントは、オペランドの値が適切な型の 1 単位分、増加させます。

int型やlong型はインクリメントすると1増えます。

int i = 12;
i++;
cout << i << endl;
long l = 34;
l++;
cout << l << endl;

Output

13
35

ポインタや配列の時に増え方が、1ではなく1単位分になります。

int *a = new int[10];

for (int i = 0; i < 10; i++)
{
    a[i] = 3 * i;
}

int *ai = &a[5];

cout << ai << endl;
cout << *ai << endl;
ai++;
cout << ai << endl;
cout << *ai << endl;

Output

0x121e06904
15
0x121e06908
18

intポインタの場合は1単位分(4)インクメントされていることがわかります。

これがshortになると、1単位分の2がインクリメントされるようになります。

short *a = new short[10];

for (int i = 0; i < 10; i++)
{
    a[i] = 100 + i;
}

short *ai = &a[5];

cout << ai << endl;
cout << *ai << endl;
ai++;
cout << ai << endl;
cout << *ai << endl;

Output

0x121e0685a
105
0x121e0685c
106

typeid演算子 typeid

構文

typeid(type-id)
typeid(expression)

typeidの結果はconst type_info&です。

int a = 123;
cout << typeid(a).name() << endl;
string s = "Hello";
cout << typeid(s).name() << endl;
char c = 'a';
cout << typeid(c).name() << endl;
HogeClass hogeClass;
cout << typeid(hogeClass).name() << endl;

Output

i
NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
c
9HogeClass

このように、直接の型名ではなく、C++側の出力しやすい形で出力されているように思います。

typeidデバッグ出力用に使うのかなと思います。

参考リスト

感想

typeidはあまり使わないかな、という感じです。 関数呼び出し演算子やインクメント/デクリメントも他の言語と同じような一般的な使い方しかしなさそうです。

次回も引き続き、演算子を行います。

そろそろC++やるぞパート21 演算子 「::」「.」「->」「[ ]」

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は演算子::,.,->,[ ]を行っていきます。

    スコープ解決演算子::

スコープの識別や、曖昧さを解決するために使われます。
例えば namespace ごとに同名の変数が定義されていた時に、{namespace名}::{変数名}とすることでアクセス先を明示的に変えることができます。

namespace NamespaceA
{
    int x;
}

namespace NamespaceB
{
    int x;
}

int main()
{
    NamespaceA::x = 1;
    NamespaceB::x = 2;
    int x = 3;
    cout << NamespaceA::x << endl;
    cout << NamespaceB::x << endl;
    cout << x << endl;

    return 0;
}

Output

1
2
3

グローバルスペースは::xのようにアクセスすることができます。

int x;
int main(){
    int x = 1;
    ::x = 1;
}

namespaceはusingディレクティブによって、変数もアクセスできるようになります。

namespace NamespaceA
{
    int x;
}

namespace NamespaceB
{
    using namespace NamespaceA;
}

int main()
{
    NamespaceB::x = 4;
    // NamespaceBはNamespaceAのxを表しているので、4を出力する
    cout << NamespaceB::x << endl;
}

静的メンバの参照

::を用いて静的メンバ(staticメンバ)にアクセスすることができます。

class MyClass
{
public:
    static int X;
};

int main()
{
    MyClass::X = 34;
}

メンバアクセス修飾子., ->

これらは、structunionclassのメンバーを参照するために使用します。

  • ポインタではない場合は.
  • ポインタの場合は->

を使用します。

class MyClass
{
public:
    MyClass(int xVal)
    {
        x = xVal;
    };
    int x;
};

int main()
{
    MyClass mc = MyClass(32);
    cout << mc.x << endl;

    MyClass *mcPtr = new MyClass(42);
    cout << mcPtr->x << endl;
}

添字演算子 [ ]

添字演算子[ ]は、[配列](の要素指定とアクセスに用います。

int intAr[5] = {3, 4, 5, 6, 7};
cout << intAr[3] << endl;

これの応用で、演算子オーバーロードにも使いま

class MyIntArray
{
public:
    MyIntArray(int size)
    {
        intArray = new int[size];
        for (int i = 0; i < size; i++)
        {
            intArray[i] = 11 * i;
        }
    };

    inline int operator[](std::size_t index) const
    {
        cout << "aain" << endl;
        return intArray[index % size];
    }
    inline int &operator[](size_t index)
    {
        cout << "iiiiin" << endl;
        return intArray[index % size];
    }

private:
    int size;
    int *intArray;
};

参考リスト

感想

スコープ解決演算子::)だけ、初めての内容でしたが、他は復習みたいな形でした!前にやったことが出てくると楽しくなってきます。

次回も演算子の続きを行います。

そろそろC++やるぞパート20 演算子

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

演算子

加減乗除+ - * /)や配列の添字演算子[ ])など、いろいろな演算をするための記号や文字です。

C++演算子は、C言語の全ての演算子にいくつかの新しい演算子が加えられてできています。

優先順位と結合規則

優先順位
複数の演算子が含む式で、どの演算子を先に演算するかの規則
結合規則
同じ優先順位の演算子を含む式ではどちらを先に演算するかの規則

例えばこの文の場合は1 + 2 * 3 / 2は式になります。

int a = 1 + 2 * 3 / 2;

優先順位は、乗算(*)と除算(/)が同じで、その下に加算(+)があります。

`*` = `/` > `+`

*/の順番は優先順位が同じなので、結合規則で決まります。これらの結合規則は左結合なので、*を先に計算します。

当たり前のようにやっていますが、このような仕組みになっています。

より複雑になったりしたら、明示的に書く場合もあります。

int a = 1 + ((2 * 3) / 2);

代替スペル/代替表現

一部の演算子の代替スペルが<iso646.h> ヘッダーにマクロとして定義されています。

例えばこのように書き換えられます。

// 変更前
if (a > 0 && a < 10)
// 変更後
if (a > 0 and a < 10)

他にもこれらが<iso646.h>に定義されています。

#define and    &&
#define and_eq &=
#define bitand &
#define bitor  |
#define compl  ~
#define not    !
#define not_eq !=
#define or     ||
#define or_eq  |=
#define xor    ^
#define xor_eq ^=

こちらにも、書いてある通り、使わずに普通に書いた方が良さそうです。

優先順位と結合規則リスト

それぞれの演算子の詳細はのちのち行います。

[ ]の中が低い方優先されます。`[3]`のみ右から左への結合なので注意が必要です。

[ 1 ]: 結合規則なし

[ 2 ]: 左から右へ結合

[ 3 ]: 右から左へ結合

[ 4 ]: 左から右へ結合

[ 5 ]: 左から右へ結合

[ 6 ]: 左から右へ結合

[ 7 ]: 左から右へ結合

[ 8 ]: 左から右へ結合

[ 9 ]: 左から右へ結合

[ 10 ]: 左から右へ結合

[ 11 ]: 左から右へ結合

  • ビット処理排他的 OR 演算子^

[ 12 ]: 左から右へ結合

  • ビット処理包括的 OR 演算子|

[ 13 ]: 左から右へ結合

[ 14 ]: 左から右へ結合

[ 15 ]: 左から右へ結合

[ 16 ]: 左から右へ結合

参考リスト

感想

基本に立ち返って演算子を行いました。基本の基本。だいたいわかりますが、メンバーへのポインター演算子.*, ->*)なんてあるのか、と思いました。

次回からは、それぞれの演算子について一つずつ使用例を確認します。

そろそろC++やるぞパート19 ポインター ~ 簡易まとめ ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は、今まで触れてきたポインタをまとめます。

ポインタ

ポインター」とも呼びますが、この記事では「ポインタ」の表記を使用します。

アドレス(メモリの位置)を持った変数のことを、ポインタ(またはポインタ変数)と呼びます。

生ポインタとスマートポインタ

C++では、いわゆる一般的なポインタである生ポインタと、リソースの管理を容易にしたスマートポインタが存在します。

生ポインタ
型名 *を用いて宣言します。

int *intPtr;
string *strPtr;

newなどで、メモリを割り当てます。

int *intPtr = new int(123);

ヒープ領域にメモリの確保するので、メモリの解放が必要です。

delete intPtr;

スマートポインタ
スマートポインタは、生ポインタと違いメモリの解放を自動で行ってくれます。スマートポインタは、3種類存在します。

  1. unique_ptr
  2. shared_ptr
  3. weak_ptr

それぞれが別々の特徴を持っているクラスです。
unique_ptrshared_ptrは、ヘルパー関数で初期化します。

auto up = make_unique<int>(234);
auto sp = make_shared<string>("Hello World");

weak_ptrshared_ptrから生成します。

weak_ptr wp = sp;

所有権

スマートポインタには、所有権という考えがあります。

所有権を普通のクラスを例にして考えてみます。以下の例では、unknownという名前のMyClassを、変数mcmc2が参照しています。

class MyClass
{
public:
    MyClass(string _name) : name(_name)
    {
    }
    string name;
};

int main()
{
    MyClass *mc = new MyClass("unknown");
    MyClass *mc2 = mc;

    return 0;
}

この参照を、所有として考えます。
つまり、いくつの変数が所有(参照)しているかです。

unique_ptr
unique_ptrは、所有権を一つしか持てません。
以下は変数upに最初に代入して、所有権(参照)はupが持っています。所有権は一つしかないので、他の変数にも代入するとエラーになります。

auto up = make_unique<int>(124);
auto up2 = up;    // コンパイルエラー

コードブロックを抜けて使われていないとメモリが解放されます。

shared_ptr shared_ptrは、所有権を複数持つことが可能です。
unique_ptrと異なり、他の変数に代入することができます。

auto up = make_shared<int>(124);
auto up2 = up;
auto up3 = up;  // 実行可能

所有権の数をカウントする、参照カウントが存在します。参照が増えた場合に増やして、コードブロックを抜けると減らします。

auto up = make_shared<int>(124);
cout << up.use_count() << endl; // 1
auto up2 = up;
cout << up.use_count() << endl; // 2
cout << up2.use_count() << endl; // 2
{
    auto up3 = up;
    cout << up.use_count() << endl; // 3
} // コードブロックを抜けるので一つ解放

cout << up.use_count() << endl; // 2

参照カウントが0になった時に、メモリの解放を行います。

weak_ptr
weak_ptrは弱いため、弱い所有権を持つことしかできません。shared_ptrから生成できますが、その時に参照カウントは増えません。

auto up = make_shared<int>(124);
cout << up.use_count() << endl;  // 1
weak_ptr wp = up;
cout << up.use_count() << endl;  // 1

これは、shared_ptrを使用して、循環参照が起こってしまう場合に主に使用します。

感想

今まで複数回にわたり触れてきて、これさえ覚えておけば、簡単に使用する分には問題なさそうなところをまとめました。(実戦ではもっと知識が必要そうですが)そこまで複雑ではなさそうというイメージです!

次回から、演算子について1から見ていこうと思います!

そろそろC++やるぞパート18 ポインター ~ スマートポインタ/weak_ptr ~

そろそろC++やります

そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。

という感じでやっています。

※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。

内容

  • 今回は、Microsoftドキュメントと他の記事を参考にして、スマートポインタの一つweak_ptrについて行います。

weak_ptrの宣言

weak_ptrは shared_ptrのリソースと同じクラステンプレートを持ちいて、shared_ptrから生成されます。

auto sp = make_shared<int>(145);
weak_ptr<int> wp = sp;

weak_ptrとは

shared_ptrが持つリソースへの弱参照を保持するクラスです。
忘れていたのですが、unique_ptrshared_ptrweak_ptrも全てクラスなんですよね。

そして、weak_ptrはshared_ptrへの所有権を持つのでなく、そのリソースを覗き見、監視します。そのため、shared_ptrの参照カウントは増えません。

auto sp = make_shared<int>(145);
cout << sp.use_count() << endl;
weak_ptr<int> wp = sp;
cout << sp.use_count() << endl;

Output

1
1

リソースの使用

リソースを使用する場合は、メンバー関数lockを呼び出すことによって作成された、shared_ptrオブジェクトを介して、使用することができます。

auto sp = make_shared<int>(145);
weak_ptr<int> wp = sp;
if (auto p = wp.lock())
{
    cout << *p << endl;
}

Output

145

lock関数で返されるshared_ptrは所有権を持ちます。

cout << sp.use_count() << endl;
if (auto p = wp.lock())
{
cout << sp.use_count() << endl;
}

Output

1
2

なぜ、「lock」という命名なのかは、分かりませんでした。

if (auto p = wp.lock())
{
    cout << sp.use_count() << endl;
    cout << *sp << endl;
    auto sp2 = p;
    if (auto p = wp.lock())
    {
        cout << sp.use_count() << endl;
        cout << *sp << endl;
    }
}

以下のようなことは問題なくできました。

  • lockの中で再度lock
  • lockの中で元のshared_ptrの使用
  • lockで得られたshared_ptrからshared_ptrの作成

get() みたいな名前でも良かったような気もしなくもないですが、lockでやって行きましょう。

循環参照 なぜweak_ptrが必要か

「weak_ptr使わないで、全てshared_ptrでやれば良い」という意見が出そうですが、循環参照に落ちいった時にweak_ptrが必要となります。

お互いをshare_ptr型のメンバ変数として持つPairClassを考えてみます。

class PairClass
{
public:
    PairClass(string _name) : name(_name)
    {
    }
    string name;
    shared_ptr<PairClass> pair;
};

int main()
{
    PairClass p1 = PairClass("pair1");
    PairClass p2 = PairClass("pair2");

    auto p1_sp = make_shared<PairClass>(p1);
    auto p2_sp = make_shared<PairClass>(p2);

    p2.pair = p1_sp;
    p1.pair = p2_sp;

    return 0;
}

こちらのp2.pair = p1_sp; p1.pair = p2_sp;の部分で、お互いに相手をshared_ptrとして保持しています。

本来であればスコープ{ }を抜けた時に、p1, p2が解放され同時に変数であるshared_ptrも解放されるはずです。しかし、p1が解放されるタイミングでp1のメンバ変数pairは使用されているため、解放されません。同様な現象がp2でも生じます。

よって、お互いに参照する循環参照が生じた時は適切にメモリが解放されないという現象が起きてしまいます。

これを対策するために、weak_ptrを使用します。

参考リスト

感想

今回はweak_ptrについて触れて行きましたが、shared_ptrの循環参照救済処置用のポインタだと分かり、使う場面も限定しそうです。

ポインタが一通り終わったので次回は一度、ポインタのまとめに入りたいと思います。